├── .eslintrc.json ├── .github └── workflows │ ├── ci.yml │ ├── commitlint.yml │ ├── docs-publish.yml │ └── npm-publish.yml ├── .gitignore ├── .husky └── commit-msg ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── THIRD_PARTY_LICENSES ├── commitlint.config.js ├── jest.config.json ├── package-lock.json ├── package.json ├── public ├── ibl │ ├── Directional_colorVariationOnTopBlur100_512.env │ ├── Neutral.env │ └── Studio_Side3Top1_256.env ├── js │ ├── basis │ │ ├── basis_transcoder.js │ │ └── basis_transcoder.wasm │ ├── draco │ │ ├── draco_decoder_gltf.js │ │ ├── draco_decoder_gltf.wasm │ │ └── draco_decoder_gltf_nodejs.js │ ├── ktx2 │ │ ├── babylon.ktx2Decoder.js │ │ ├── msc_basis_transcoder.js │ │ ├── msc_basis_transcoder.wasm │ │ ├── uastc_astc.wasm │ │ ├── uastc_bc7.wasm │ │ ├── uastc_rgba8_srgb_v2.wasm │ │ ├── uastc_rgba8_unorm_v2.wasm │ │ └── zstddec.wasm │ └── meshopt │ │ └── meshopt_decoder.js └── model │ └── mannequin.glb ├── scripts ├── publish.js └── updateBabylonjsVersion.js ├── src ├── camera │ ├── camera.ts │ └── index.ts ├── config │ ├── cameraAnimationConfig.ts │ ├── cameraConfig.ts │ ├── config.ts │ ├── extensionConfig.ts │ ├── index.ts │ ├── lightingConfig.ts │ ├── preset │ │ ├── defaultConfig.ts │ │ ├── index.ts │ │ └── v3dConfig.ts │ ├── renderingPipelineConfig.ts │ └── sceneConfig.ts ├── dev │ ├── index.ts │ ├── v3dViewer.ts │ ├── viewer.css │ └── viewer.html ├── index.ts ├── lighting │ ├── index.ts │ └── lighting.ts ├── manager │ ├── index.ts │ ├── observableManager.ts │ └── sceneManager.ts ├── material │ ├── depthMaterial.ts │ └── index.ts ├── model │ ├── index.ts │ ├── model.ts │ └── modelLoader.ts ├── scene │ ├── abstractScene.ts │ ├── defaultScene.ts │ ├── index.ts │ └── v3dScene.ts └── util │ ├── convex-hull.d.ts │ ├── convexHull.ts │ └── index.ts ├── test ├── camera │ └── camera.test.ts ├── lighting │ └── lighting.test.ts ├── manager │ └── sceneManager.test.ts ├── material │ └── depthMaterial.test.ts ├── model │ ├── model.test.ts │ └── modelLoader.test.ts ├── scene │ ├── defaultScene.test.ts │ └── v3dScene.test.ts └── util │ ├── convexHull.test.ts │ └── index.test.ts ├── tsconfig.json └── webpack.config.mjs /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": ["/*", "!/src", "!/test"], 3 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"], 4 | "parser": "@typescript-eslint/parser", 5 | "plugins": ["import", "@typescript-eslint", "import-path"], 6 | "rules": { 7 | "@typescript-eslint/ban-ts-comment": "warn", 8 | "@typescript-eslint/member-ordering": "error", 9 | "@typescript-eslint/no-explicit-any": "warn", 10 | "@typescript-eslint/no-unused-vars": "warn", 11 | "arrow-body-style": "off", 12 | "prefer-arrow-callback": "off", 13 | "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0 }], 14 | "import/order": [ 15 | "error", 16 | { 17 | "newlines-between": "always", 18 | "alphabetize": { "order": "asc", "caseInsensitive": true } 19 | } 20 | ], 21 | "import-path/forbidden": ["error", [ 22 | { 23 | "match": "^@babylonjs/(core|gui|loaders|materials)$", 24 | "message": "Use full path to benefit from tree-shaking, https://doc.babylonjs.com/setup/frameworkPackages/es6Support#tree-shaking. For example, 'import { Engine } from \"@babylonjs/core/Engines/engine\";'" 25 | } 26 | ]], 27 | "prettier/prettier": [ 28 | "error", 29 | { 30 | "printWidth": 120, 31 | "tabWidth": 4, 32 | "singleQuote": true, 33 | "trailingComma": "all" 34 | } 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Run continuous integration tests 2 | 3 | on: [push, pull_request, merge_group] 4 | 5 | jobs: 6 | ci: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout the repository 10 | uses: actions/checkout@v4 11 | with: 12 | fetch-depth: 0 13 | 14 | - name: Set up Node.js 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: 20 18 | 19 | - name: Install 20 | run: npm ci 21 | 22 | - name: Build 23 | run: npm run build 24 | 25 | - name: Run unit tests 26 | run: npm run test 27 | 28 | - name: Test on generating docs 29 | run: npm run docs 30 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | name: Run commitlint on git commit messages 2 | 3 | on: [push, pull_request, merge_group] 4 | 5 | jobs: 6 | commitlint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout the repository 10 | uses: actions/checkout@v4 11 | with: 12 | fetch-depth: 0 13 | 14 | - name: Set up Node.js 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: 20 18 | 19 | - name: Print versions 20 | run: | 21 | git --version 22 | node --version 23 | npm --version 24 | npx commitlint --version 25 | 26 | - name: Install commitlint 27 | run: | 28 | npm install conventional-changelog-conventionalcommits 29 | npm install commitlint@latest 30 | 31 | - name: Validate current commit (last commit) with commitlint 32 | if: github.event_name == 'push' 33 | run: npx commitlint --last --verbose 34 | 35 | - name: Validate PR commits with commitlint 36 | if: github.event_name == 'pull_request' 37 | run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose 38 | -------------------------------------------------------------------------------- /.github/workflows/docs-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will publish API docs to GitHub Pages when a new release tag is added to the main branch 2 | 3 | name: Publish API docs to GitHub Pages 4 | 5 | on: 6 | release: 7 | types: 8 | - published 9 | workflow_dispatch: 10 | 11 | jobs: 12 | check-tag: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Fetch the latest release tag 16 | id: fetch_latest_release_tag 17 | run: | 18 | LATEST_TAG=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name') 19 | echo "LATEST_TAG=$LATEST_TAG" >> "$GITHUB_OUTPUT" 20 | echo "The latest release tag is $LATEST_TAG" 21 | 22 | - name: Check release tag on release event 23 | if: ${{ github.event_name == 'release' }} 24 | env: 25 | RELEASE_TAG: ${{ github.event.release.tag_name }} 26 | LATEST_TAG: ${{ steps.fetch_latest_release_tag.outputs.LATEST_TAG }} 27 | run: | 28 | if [[ "$RELEASE_TAG" != "$LATEST_TAG" ]]; then 29 | echo "Release tag $RELEASE_TAG does not match the latest tag $LATEST_TAG." 30 | exit 1 31 | else 32 | echo "Release tag $RELEASE_TAG matches the latest tag $LATEST_TAG." 33 | fi 34 | 35 | build: 36 | needs: check-tag 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: Checkout the repository 40 | uses: actions/checkout@v4 41 | with: 42 | fetch-depth: 0 43 | ref: ${{ steps.fetch_latest_release_tag.outputs.LATEST_TAG }} 44 | 45 | - name: Set up Node.js 46 | uses: actions/setup-node@v4 47 | with: 48 | node-version: 20 49 | 50 | - name: Install 51 | run: npm ci 52 | 53 | - name: Build docs 54 | run: npm run docs 55 | 56 | - name: Run webpack 57 | run: npm run webpack 58 | 59 | - name: Create .nojekyll file 60 | run: touch web/.nojekyll 61 | 62 | - name: Upload static files as artifact 63 | uses: actions/upload-pages-artifact@v3 64 | with: 65 | path: web/ 66 | 67 | deploy: 68 | needs: build 69 | 70 | concurrency: 71 | group: docs-publish 72 | cancel-in-progress: true 73 | 74 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment 75 | permissions: 76 | pages: write # to deploy to Pages 77 | id-token: write # to verify the deployment originates from an appropriate source 78 | 79 | # Deploy to the github-pages environment 80 | environment: 81 | name: github-pages 82 | url: ${{ steps.deployment.outputs.page_url }} 83 | 84 | runs-on: ubuntu-latest 85 | steps: 86 | - name: Deploy to GitHub Pages 87 | id: deployment 88 | uses: actions/deploy-pages@v4 89 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will publish a package to NPM registry when a release is published 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Publish to NPM on release 5 | 6 | on: 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout the repository at the release tag 16 | uses: actions/checkout@v4 17 | with: 18 | ref: ${{ github.event.release.tag_name }} 19 | 20 | - name: Set up Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: 20 24 | registry-url: https://registry.npmjs.org/ 25 | 26 | - name: Publish the package to NPM 27 | run: npm run publish:dist 28 | env: 29 | NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | *.swp 3 | .DS_Store 4 | lib 5 | node_modules 6 | dist 7 | .idea 8 | coverage 9 | *.tgz 10 | docs 11 | web 12 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no -- commitlint --edit $1 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.1.2 4 | - fix: fallback to full download on HTTP range request failure ([#13](https://github.com/amzn/lib-3d-scene-viewer/pull/13)) 5 | - docs: lazy load @babylonjs/inspector ([#14](https://github.com/amzn/lib-3d-scene-viewer/pull/14)) 6 | 7 | ## 1.1.1 8 | - ci: add a GitHub workflow to run continuous integration tests ([#10](https://github.com/amzn/lib-3d-scene-viewer/pull/10)) 9 | - docs: add sandbox to GitHub pages ([#11](https://github.com/amzn/lib-3d-scene-viewer/pull/11)) 10 | 11 | ## 1.1.0 12 | - fix: fix a memory leak issue on Model.removeFromScene() API ([#7](https://github.com/amzn/lib-3d-scene-viewer/pull/7)) 13 | - ci: add husky and commitlint for checking git commit messages ([#8](https://github.com/amzn/lib-3d-scene-viewer/pull/8)) 14 | - chore(deps): bump up BabylonJS version 7.49.0 ([#6](https://github.com/amzn/lib-3d-scene-viewer/pull/6)) 15 | 16 | ## 1.0.1 17 | - Updated the preset lighting in V3D_CONFIG 18 | - Added keywords to package.json 19 | - Updated README.md 20 | 21 | ## 1.0.0 22 | - BabylonJS version: [`7.34.2`](https://github.com/BabylonJS/Babylon.js/releases/tag/7.34.2) 23 | - Initialized the repository 24 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/amzn/lib-3d-scene-viewer/issues), or [recently closed](https://github.com/amzn/lib-3d-scene-viewer/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | 25 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 26 | 27 | 1. You are working against the latest source on the *main* branch. 28 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 29 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 30 | 31 | To send us a pull request, please: 32 | 33 | 1. Fork the repository. 34 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 35 | 3. Add API documentation to all added public functions and classes using TSDoc syntax. 36 | 4. Run `npm run build` locally and make sure it's successfully executed. 37 | 5. Run `npm run test` for unit tests. Ensure local tests pass. 38 | 6. Commit to your fork using clear commit messages. 39 | 7. Send us a pull request, answering any default questions in the pull request interface. 40 | 8. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 41 | 42 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 43 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 44 | 45 | 46 | ## Backward Compatibility 47 | 48 | Any code change that breaks backward compatibility should be avoided. 49 | You can always add to an API, but cannot remove anything from one. 50 | 51 | 52 | ## Development 53 | 54 | See [README.md](README.md#development). 55 | 56 | 57 | ## Code Style 58 | 59 | We use ESLint on the code to ensure a consistent style. 60 | Any new code committed must pass our ESLint tests by running: 61 | 62 | ``` 63 | npm run lint 64 | ``` 65 | 66 | 67 | ## Documentation 68 | 69 | All public APIs must have API documentation. 70 | We use `typedoc` to generate documentation files: 71 | 72 | ``` 73 | npm run docs 74 | ``` 75 | 76 | The files are generated under `./dist/docs/` folder. 77 | 78 | 79 | ## Code of Conduct 80 | 81 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 82 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 83 | opensource-codeofconduct@amazon.com with any additional questions or comments. 84 | 85 | 86 | ## Licensing 87 | 88 | See the [LICENSE](https://github.com/amzn/lib-3d-scene-viewer/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 89 | 90 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | lib-3d-scene-viewer 2 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lib-3d-scene-viewer 2 | 3 | ![NPM Version](https://img.shields.io/npm/v/%40amazon%2Flib-3d-scene-viewer?color=green) 4 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 5 | 6 | **lib-3d-scene-viewer** is a package based on [Babylon.js](https://www.babylonjs.com/). 7 | It provides preset configurations for quickly setting up a 3D scene viewer, 8 | which offers a 3D environment similar to the "View in 3D" CX on Amazon Product Detail Pages. 9 | 10 | 11 | ## Table of Contents 12 | 13 | - [Getting Started](#getting-started) 14 | - [Installation](#installation) 15 | - [Usage](#usage) 16 | - [Resource](#resource) 17 | - [Config](#config) 18 | - [Sandbox](#sandbox) 19 | - [Documentation](#documentation) 20 | - [Development](#development) 21 | - [Contributing](#contributing) 22 | - [License](#license) 23 | 24 | 25 | ## Getting Started 26 | 27 | ### Installation 28 | 29 | The package can be installed from [npm](https://npmjs.org/): 30 | 31 | ```shell 32 | npm install @amazon/lib-3d-scene-viewer 33 | ``` 34 | 35 | 36 | ### Usage 37 | 38 | [Take a look at an example](src/dev/v3dViewer.ts) 39 | 40 | ```ts 41 | import { Config } from '@amazon/lib-3d-scene-viewer/config/config'; 42 | import { Model } from '@amazon/lib-3d-scene-viewer/model/model'; 43 | import { Scene } from '@babylonjs/core/scene'; 44 | import { V3D_CONFIG } from '@amazon/lib-3d-scene-viewer/config/preset/v3dConfig'; 45 | import { V3DScene } from '@amazon/lib-3d-scene-viewer/scene/v3dScene'; 46 | 47 | (async function () { 48 | ///////////////////////////////////////// 49 | // Step 0: create a canvas DOM element // 50 | ///////////////////////////////////////// 51 | 52 | const canvas = document.createElement('canvas'); 53 | document.body.appendChild(canvas); 54 | 55 | //////////////////////////////////////////// 56 | // Step 1: create an instance of V3DScene // 57 | //////////////////////////////////////////// 58 | 59 | // V3D_CONFIG is a preset config 60 | const v3dScene = new V3DScene(canvas, V3D_CONFIG, { 61 | // Override file paths if needed 62 | lightingConfig: { 63 | DirectionalBlur: { 64 | type: 'env', 65 | filePath: 'public/ibl/Directional_colorVariationOnTopBlur100_512.env', 66 | }, 67 | }, 68 | basisTranscoder: { 69 | urlConfig: { 70 | jsModuleUrl: 'public/js/basis/basis_transcoder.js', 71 | wasmModuleUrl: 'public/js/basis/basis_transcoder.wasm', 72 | }, 73 | }, 74 | ktx2Decoder: { 75 | urlConfig: { 76 | jsDecoderModule: 'public/js/ktx2/babylon.ktx2Decoder.js', 77 | jsMSCTranscoder: 'public/js/ktx2/msc_basis_transcoder.js', 78 | wasmMSCTranscoder: 'public/js/ktx2/msc_basis_transcoder.wasm', 79 | wasmUASTCToASTC: 'public/js/ktx2/uastc_astc.wasm', 80 | wasmUASTCToBC7: 'public/js/ktx2/uastc_bc7.wasm', 81 | wasmUASTCToR8_UNORM: null, 82 | wasmUASTCToRG8_UNORM: null, 83 | wasmUASTCToRGBA_SRGB: 'public/js/ktx2/uastc_rgba8_srgb_v2.wasm', 84 | wasmUASTCToRGBA_UNORM: 'public/js/ktx2/uastc_rgba8_unorm_v2.wasm', 85 | wasmZSTDDecoder: 'public/js/ktx2/zstddec.wasm', 86 | }, 87 | }, 88 | dracoCompression: { 89 | decoders: { 90 | wasmBinaryUrl: 'public/js/draco/draco_decoder_gltf.wasm', 91 | wasmUrl: 'public/js/draco/draco_decoder_gltf_nodejs.js', 92 | fallbackUrl: 'public/js/draco/draco_decoder_gltf.js', 93 | }, 94 | }, 95 | meshoptCompression: { 96 | decoder: { 97 | url: 'public/js/meshopt/meshopt_decoder.js', 98 | }, 99 | }, 100 | enableDragAndDrop: true, 101 | }); 102 | 103 | ///////////////////////////////////////////////////////////////////// 104 | // Step 2: register any observers using V3DScene.observableManager // 105 | ///////////////////////////////////////////////////////////////////// 106 | 107 | v3dScene.observableManager.onConfigChangedObservable.add((config: Config) => { 108 | console.log('Updated config:', config); 109 | }); 110 | 111 | v3dScene.observableManager.onModelLoadedObservable.add((model) => { 112 | model.showShadowOnGroundDepthMap(); 113 | model.moveCenterToTargetCoordinate(); 114 | 115 | const radius = 2 * Math.max(...model.getOverallBoundingBoxDimensions().asArray()); 116 | v3dScene.updateConfig({ 117 | cameraConfig: { 118 | ArcRotateCamera: { 119 | type: 'arcRotateCamera', 120 | radius: radius, 121 | lowerRadiusLimit: radius * 0.05, 122 | upperRadiusLimit: radius * 5, 123 | minZ: radius * 0.02, 124 | maxZ: radius * 40, 125 | }, 126 | }, 127 | }); 128 | }); 129 | 130 | ////////////////////////////////// 131 | // Step 3: call init() function // 132 | ////////////////////////////////// 133 | 134 | await v3dScene.init(); 135 | 136 | ///////////////////////////////// 137 | // Step 4: load glTF/glb model // 138 | ///////////////////////////////// 139 | 140 | const model: Model = await v3dScene.loadGltf('public/model/mannequin.glb', true); 141 | console.log('Bounding box dimensions:', model.getOverallBoundingBoxDimensions()); 142 | 143 | ////////////////////////////////////////////////////////////////////////////////////////////////// 144 | // Step 5 (Optional): call updateConfig() to update scene setup and/or handle user interactions // 145 | ////////////////////////////////////////////////////////////////////////////////////////////////// 146 | 147 | await v3dScene.updateConfig({ 148 | sceneConfig: { 149 | useLoadingUI: true, 150 | }, 151 | }); 152 | 153 | // Access BabylonJS scene object 154 | const babylonScene: Scene = v3dScene.scene; 155 | console.log('Active Cameras:', babylonScene.activeCameras); 156 | 157 | // Toggle BabylonJS debug layer 158 | document.addEventListener('keydown', async (event) => { 159 | const key = event.key; 160 | // Pressing '?' should show/hide the debug layer 161 | if (key === '?') { 162 | // Needed for BabylonJS debug layer 163 | import('@babylonjs/inspector').then(() => { 164 | v3dScene.toggleDebugMode(); 165 | }); 166 | } 167 | }); 168 | })(); 169 | ``` 170 | 171 | 172 | ### Resource 173 | 174 | This package provides a few resources including IBL files, decoder/transcoder files, and 3D models. 175 | These resources can be found in [public](public) folder or `@amazon/lib-3d-scene-viewer/public` via npm. 176 | 177 | 178 | ### Config 179 | 180 | This packages uses [Config](src/config/config.ts) to set up engine, scene, camera, lighting, decoder files, etc. 181 | 182 | The full config parameters and default values can be found in [Config](src/config/config.ts). 183 | 184 | It also provides preset config files in [preset](src/config/preset) folder 185 | or `@amazon/lib-3d-scene-viewer/config/preset`. 186 | 187 | 188 | ## Sandbox 189 | 190 | Try loading GLB/GLTF models into the [Sandbox](https://amzn.github.io/lib-3d-scene-viewer/v3dViewer.html). 191 | 192 | 193 | ## Documentation 194 | 195 | - [API Docs](https://amzn.github.io/lib-3d-scene-viewer/docs/index.html) 196 | 197 | 198 | ## Development 199 | 200 | When developing the project, first install 201 | [git](https://git-scm.com), 202 | [Node.js](https://nodejs.org) 203 | and [npm](https://www.npmjs.com/). 204 | 205 | Then, follow the steps to set up the development environment: 206 | 207 | ```shell 208 | git clone git@github.com:amzn/lib-3d-scene-viewer.git 209 | cd lib-3d-scene-viewer 210 | npm install 211 | ``` 212 | 213 | The following scripts are available: 214 | 215 | | Command | Description | 216 | |--------------------------|---------------------------------------------------------------------------------------------| 217 | | `npm install` | Install dependencies | 218 | | `npm run build` | Run the build step for all sub-projects | 219 | | `npm run clean` | Remove all built artifacts | 220 | | `npm run docs` | Create API documentation | 221 | | `npm run lint` | Run ESLint | 222 | | `npm run pack:dist` | Build the project and create an npm tarball under `dist` folder | 223 | | `npm run publish:dist` | Publish the npm tarball | 224 | | `npm run server` | Run a web server and open a new browser tab pointed to [src/dev/index.ts](src/dev/index.ts) | 225 | | `npm run test` | Run tests | 226 | | `npm run update-bjs-ver` | Update BabylonJS dependencies to a specific version | 227 | | `npm run webpack` | Generate static web resources for sandbox and API docs | 228 | 229 | 230 | ## Contributing 231 | 232 | For more information take a look at [CONTRIBUTING.md](CONTRIBUTING.md). 233 | 234 | 235 | ## License 236 | 237 | This library is licensed under the [Apache 2.0](LICENSE) License. 238 | -------------------------------------------------------------------------------- /THIRD_PARTY_LICENSES: -------------------------------------------------------------------------------- 1 | ** Babylon.js -- https://github.com/BabylonJS/Babylon.js 2 | Babylon.js Copyright 2023 The Babylon.js team 3 | ** draco3dgltf -- https://github.com/google/draco 4 | Copyright 2021 The Draco Authors 5 | ** Basis-Universal-Transcoders -- https://github.com/KhronosGroup/Basis-Universal-Transcoders 6 | 7 | Apache License 8 | Version 2.0, January 2004 9 | http://www.apache.org/licenses/ 10 | 11 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 12 | 13 | 1. Definitions. 14 | 15 | "License" shall mean the terms and conditions for use, reproduction, and 16 | distribution as defined by Sections 1 through 9 of this document. 17 | 18 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 19 | owner that is granting the License. 20 | 21 | "Legal Entity" shall mean the union of the acting entity and all other entities 22 | that control, are controlled by, or are under common control with that entity. 23 | For the purposes of this definition, "control" means (i) the power, direct or 24 | indirect, to cause the direction or management of such entity, whether by 25 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 26 | outstanding shares, or (iii) beneficial ownership of such entity. 27 | 28 | "You" (or "Your") shall mean an individual or Legal Entity exercising 29 | permissions granted by this License. 30 | 31 | "Source" form shall mean the preferred form for making modifications, including 32 | but not limited to software source code, documentation source, and configuration 33 | files. 34 | 35 | "Object" form shall mean any form resulting from mechanical transformation or 36 | translation of a Source form, including but not limited to compiled object code, 37 | generated documentation, and conversions to other media types. 38 | 39 | "Work" shall mean the work of authorship, whether in Source or Object form, made 40 | available under the License, as indicated by a copyright notice that is included 41 | in or attached to the work (an example is provided in the Appendix below). 42 | 43 | "Derivative Works" shall mean any work, whether in Source or Object form, that 44 | is based on (or derived from) the Work and for which the editorial revisions, 45 | annotations, elaborations, or other modifications represent, as a whole, an 46 | original work of authorship. For the purposes of this License, Derivative Works 47 | shall not include works that remain separable from, or merely link (or bind by 48 | name) to the interfaces of, the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including the original version 51 | of the Work and any modifications or additions to that Work or Derivative Works 52 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 53 | by the copyright owner or by an individual or Legal Entity authorized to submit 54 | on behalf of the copyright owner. For the purposes of this definition, 55 | "submitted" means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, and 58 | issue tracking systems that are managed by, or on behalf of, the Licensor for 59 | the purpose of discussing and improving the Work, but excluding communication 60 | that is conspicuously marked or otherwise designated in writing by the copyright 61 | owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 64 | of whom a Contribution has been received by Licensor and subsequently 65 | incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of this 68 | License, each Contributor hereby grants to You a perpetual, worldwide, non- 69 | exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, 70 | prepare Derivative Works of, publicly display, publicly perform, sublicense, and 71 | distribute the Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of this License, 74 | each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no- 75 | charge, royalty-free, irrevocable (except as stated in this section) patent 76 | license to make, have made, use, offer to sell, sell, import, and otherwise 77 | transfer the Work, where such license applies only to those patent claims 78 | licensable by such Contributor that are necessarily infringed by their 79 | Contribution(s) alone or by combination of their Contribution(s) with the Work 80 | to which such Contribution(s) was submitted. If You institute patent litigation 81 | against any entity (including a cross-claim or counterclaim in a lawsuit) 82 | alleging that the Work or a Contribution incorporated within the Work 83 | constitutes direct or contributory patent infringement, then any patent licenses 84 | granted to You under this License for that Work shall terminate as of the date 85 | such litigation is filed. 86 | 87 | 4. Redistribution. You may reproduce and distribute copies of the Work or 88 | Derivative Works thereof in any medium, with or without modifications, and in 89 | Source or Object form, provided that You meet the following conditions: 90 | 91 | (a) You must give any other recipients of the Work or Derivative Works a 92 | copy of this License; and 93 | 94 | (b) You must cause any modified files to carry prominent notices stating 95 | that You changed the files; and 96 | 97 | (c) You must retain, in the Source form of any Derivative Works that You 98 | distribute, all copyright, patent, trademark, and attribution notices from the 99 | Source form of the Work, excluding those notices that do not pertain to any part 100 | of the Derivative Works; and 101 | 102 | (d) If the Work includes a "NOTICE" text file as part of its distribution, 103 | then any Derivative Works that You distribute must include a readable copy of 104 | the attribution notices contained within such NOTICE file, excluding those 105 | notices that do not pertain to any part of the Derivative Works, in at least one 106 | of the following places: within a NOTICE text file distributed as part of the 107 | Derivative Works; within the Source form or documentation, if provided along 108 | with the Derivative Works; or, within a display generated by the Derivative 109 | Works, if and wherever such third-party notices normally appear. The contents of 110 | the NOTICE file are for informational purposes only and do not modify the 111 | License. You may add Your own attribution notices within Derivative Works that 112 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 113 | provided that such additional attribution notices cannot be construed as 114 | modifying the License. 115 | 116 | You may add Your own copyright statement to Your modifications and may 117 | provide additional or different license terms and conditions for use, 118 | reproduction, or distribution of Your modifications, or for any such Derivative 119 | Works as a whole, provided Your use, reproduction, and distribution of the Work 120 | otherwise complies with the conditions stated in this License. 121 | 122 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 123 | Contribution intentionally submitted for inclusion in the Work by You to the 124 | Licensor shall be under the terms and conditions of this License, without any 125 | additional terms or conditions. Notwithstanding the above, nothing herein shall 126 | supersede or modify the terms of any separate license agreement you may have 127 | executed with Licensor regarding such Contributions. 128 | 129 | 6. Trademarks. This License does not grant permission to use the trade names, 130 | trademarks, service marks, or product names of the Licensor, except as required 131 | for reasonable and customary use in describing the origin of the Work and 132 | reproducing the content of the NOTICE file. 133 | 134 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in 135 | writing, Licensor provides the Work (and each Contributor provides its 136 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 137 | KIND, either express or implied, including, without limitation, any warranties 138 | or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 139 | PARTICULAR PURPOSE. You are solely responsible for determining the 140 | appropriateness of using or redistributing the Work and assume any risks 141 | associated with Your exercise of permissions under this License. 142 | 143 | 8. Limitation of Liability. In no event and under no legal theory, whether in 144 | tort (including negligence), contract, or otherwise, unless required by 145 | applicable law (such as deliberate and grossly negligent acts) or agreed to in 146 | writing, shall any Contributor be liable to You for damages, including any 147 | direct, indirect, special, incidental, or consequential damages of any character 148 | arising as a result of this License or out of the use or inability to use the 149 | Work (including but not limited to damages for loss of goodwill, work stoppage, 150 | computer failure or malfunction, or any and all other commercial damages or 151 | losses), even if such Contributor has been advised of the possibility of such 152 | damages. 153 | 154 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or 155 | Derivative Works thereof, You may choose to offer, and charge a fee for, 156 | acceptance of support, warranty, indemnity, or other liability obligations 157 | and/or rights consistent with this License. However, in accepting such 158 | obligations, You may act only on Your own behalf and on Your sole 159 | responsibility, not on behalf of any other Contributor, and only if You agree to 160 | indemnify, defend, and hold each Contributor harmless for any liability incurred 161 | by, or claims asserted against, such Contributor by reason of your accepting any 162 | such warranty or additional liability. 163 | 164 | END OF TERMS AND CONDITIONS 165 | 166 | APPENDIX: How to apply the Apache License to your work. 167 | 168 | To apply the Apache License to your work, attach the following boilerplate 169 | notice, with the fields enclosed by brackets "[]" replaced with your own 170 | identifying information. (Don't include the brackets!) The text should be 171 | enclosed in the appropriate comment syntax for the file format. We also 172 | recommend that a file or class name and description of purpose be included on 173 | the same "printed page" as the copyright notice for easier identification within 174 | third-party archives. 175 | 176 | Copyright [yyyy] [name of copyright owner] 177 | 178 | Licensed under the Apache License, Version 2.0 (the "License"); 179 | you may not use this file except in compliance with the License. 180 | You may obtain a copy of the License at 181 | 182 | http://www.apache.org/licenses/LICENSE-2.0 183 | 184 | Unless required by applicable law or agreed to in writing, software 185 | distributed under the License is distributed on an "AS IS" BASIS, 186 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 187 | See the License for the specific language governing permissions and 188 | limitations under the License. 189 | 190 | * For Babylon.js see also this required NOTICE: 191 | Babylon.js Copyright 2023 The Babylon.js team 192 | * For draco3dgltf see also this required NOTICE: 193 | Copyright 2021 The Draco Authors 194 | 195 | ------ 196 | 197 | ** deepmerge-ts -- https://github.com/RebeccaStevens/deepmerge-ts 198 | Copyright (c) 2021, Rebecca Stevens. All rights reserved. 199 | 200 | BSD 3-Clause License 201 | 202 | Copyright (c) . All rights reserved. 203 | 204 | Redistribution and use in source and binary forms, with or without 205 | modification, are permitted provided that the following conditions are met: 206 | 207 | 1. Redistributions of source code must retain the above copyright notice, this 208 | list of conditions and the following disclaimer. 209 | 210 | 2. Redistributions in binary form must reproduce the above copyright notice, 211 | this list of conditions and the following disclaimer in the documentation 212 | and/or other materials provided with the distribution. 213 | 214 | 3. Neither the name of the copyright holder nor the names of its 215 | contributors may be used to endorse or promote products derived from 216 | this software without specific prior written permission. 217 | 218 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 219 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 220 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 221 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 222 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 223 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 224 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 225 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 226 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 227 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 228 | 229 | ------ 230 | 231 | ** convex-hull -- https://github.com/mikolalysenko/convex-hull 232 | Copyright (c) 2014 Mikola Lysenko 233 | ** meshoptimizer -- https://github.com/zeux/meshoptimizer 234 | Copyright (c) 2016-2024 Arseny Kapoulkine 235 | 236 | MIT License 237 | 238 | Copyright (c) 239 | 240 | Permission is hereby granted, free of charge, to any person obtaining a copy of 241 | this software and associated documentation files (the "Software"), to deal in 242 | the Software without restriction, including without limitation the rights to 243 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 244 | the Software, and to permit persons to whom the Software is furnished to do so, 245 | subject to the following conditions: 246 | 247 | The above copyright notice and this permission notice shall be included in all 248 | copies or substantial portions of the Software. 249 | 250 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 251 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 252 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 253 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 254 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 255 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 256 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'body-max-line-length': [2, 'always', 140], 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "ts-jest/presets/default-esm", 3 | "testEnvironment": "jsdom", 4 | "setupFiles": ["jest-canvas-mock"], 5 | "moduleDirectories": [ 6 | "node_modules", 7 | "src" 8 | ], 9 | "transform": { 10 | "^.+\\.tsx?$": [ 11 | "ts-jest", 12 | { 13 | "useESM": true, 14 | "tsconfig": { 15 | "esModuleInterop": true 16 | } 17 | } 18 | ] 19 | }, 20 | "collectCoverage": true, 21 | "coverageReporters": [ 22 | "cobertura", 23 | "lcov", 24 | "json-summary", 25 | "html", 26 | "text" 27 | ], 28 | "coverageThreshold": { 29 | "global": { 30 | "branches": 75, 31 | "functions": 85, 32 | "lines": 85, 33 | "statements": 85 34 | } 35 | }, 36 | "coverageDirectory": "coverage" 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@amazon/lib-3d-scene-viewer", 3 | "version": "1.1.2", 4 | "description": "This package provides preset configurations for quickly setting up a 3D scene viewer.", 5 | "scripts": { 6 | "build": "rimraf dist && npm run lint && tsc -b tsconfig.json && npm run copy-files", 7 | "clean": "rimraf node_modules dist coverage docs", 8 | "copy-files": "cpy public/* package.json LICENSE README.md THIRD_PARTY_LICENSES dist", 9 | "docs": "typedoc", 10 | "format": "eslint '**/*.{ts,tsx}' --fix", 11 | "lint": "eslint '**/*.{ts,tsx}' --quiet", 12 | "pack:dist": "npm run build && npm run test && npm run docs && cd dist && npm pack && cd ..", 13 | "prepare": "husky", 14 | "prepublishOnly": "echo '\"npm publish\" is disabled. Please use \"npm run publish:dist\" to publish the package.' && exit 1", 15 | "publish:dist": "node ./scripts/publish.js", 16 | "server": "webpack serve", 17 | "test": "NODE_OPTIONS=--experimental-vm-modules npx jest --verbose", 18 | "update-bjs-ver": "node ./scripts/updateBabylonjsVersion.js", 19 | "webpack": "webpack --env production" 20 | }, 21 | "author": "Amazon", 22 | "license": "Apache-2.0", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/amzn/lib-3d-scene-viewer" 26 | }, 27 | "keywords": [ 28 | "3d", 29 | "webgl", 30 | "webgl2", 31 | "javascript", 32 | "canvas", 33 | "amazon" 34 | ], 35 | "files": [ 36 | "**/*.js", 37 | "**/*.d.ts", 38 | "**/*.map", 39 | "public/**/*", 40 | "LICENSE", 41 | "README.md", 42 | "THIRD_PARTY_LICENSES" 43 | ], 44 | "engines": { 45 | "npm": ">=10.0.0", 46 | "node": ">=18.0.0" 47 | }, 48 | "devDependencies": { 49 | "@babylonjs/inspector": "7.49.0", 50 | "@commitlint/cli": "^19.7.1", 51 | "@commitlint/config-conventional": "^19.7.1", 52 | "@types/jest": "^29.5.13", 53 | "@typescript-eslint/eslint-plugin": "^8.7.0", 54 | "@typescript-eslint/parser": "^8.7.0", 55 | "copy-webpack-plugin": "^13.0.0", 56 | "cpy-cli": "^5.0.0", 57 | "css-loader": "^7.1.2", 58 | "draco3dgltf": "1.5.7", 59 | "eslint": "^8.57.1", 60 | "eslint-config-prettier": "^9.1.0", 61 | "eslint-import-resolver-typescript": "^3.6.3", 62 | "eslint-plugin-import": "^2.30.0", 63 | "eslint-plugin-import-path": "^0.0.2", 64 | "eslint-plugin-prettier": "^5.2.1", 65 | "file-loader": "^6.2.0", 66 | "html-webpack-plugin": "^5.6.0", 67 | "husky": "^9.1.7", 68 | "jest": "^29.7.0", 69 | "jest-canvas-mock": "^2.5.2", 70 | "jest-environment-jsdom": "^29.7.0", 71 | "nock": "^13.5.5", 72 | "prettier": "3.3.3", 73 | "rimraf": "^4.4.1", 74 | "style-loader": "^4.0.0", 75 | "ts-jest": "^29.2.5", 76 | "ts-loader": "^9.5.1", 77 | "typedoc": "^0.26.8", 78 | "typescript": "~5.5.0", 79 | "webpack": "^5.94.0", 80 | "webpack-cli": "^5.1.4", 81 | "webpack-dev-server": "^4.15.2" 82 | }, 83 | "dependencies": { 84 | "@babylonjs/core": "7.49.0", 85 | "@babylonjs/gui": "7.49.0", 86 | "@babylonjs/loaders": "7.49.0", 87 | "@babylonjs/materials": "7.49.0", 88 | "convex-hull": "1.0.3", 89 | "deepmerge-ts": "4.3.0" 90 | }, 91 | "peerDependencies": { 92 | "@babylonjs/core": ">= 7.49.0 < 8", 93 | "@babylonjs/gui": ">= 7.49.0 < 8", 94 | "@babylonjs/loaders": ">= 7.49.0 < 8", 95 | "@babylonjs/materials": ">= 7.49.0 < 8" 96 | } 97 | } -------------------------------------------------------------------------------- /public/ibl/Directional_colorVariationOnTopBlur100_512.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/lib-3d-scene-viewer/b3a5e268b77f5887dded4054d5810fbe9937cc04/public/ibl/Directional_colorVariationOnTopBlur100_512.env -------------------------------------------------------------------------------- /public/ibl/Neutral.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/lib-3d-scene-viewer/b3a5e268b77f5887dded4054d5810fbe9937cc04/public/ibl/Neutral.env -------------------------------------------------------------------------------- /public/ibl/Studio_Side3Top1_256.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/lib-3d-scene-viewer/b3a5e268b77f5887dded4054d5810fbe9937cc04/public/ibl/Studio_Side3Top1_256.env -------------------------------------------------------------------------------- /public/js/basis/basis_transcoder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/lib-3d-scene-viewer/b3a5e268b77f5887dded4054d5810fbe9937cc04/public/js/basis/basis_transcoder.wasm -------------------------------------------------------------------------------- /public/js/draco/draco_decoder_gltf.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/lib-3d-scene-viewer/b3a5e268b77f5887dded4054d5810fbe9937cc04/public/js/draco/draco_decoder_gltf.wasm -------------------------------------------------------------------------------- /public/js/ktx2/msc_basis_transcoder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/lib-3d-scene-viewer/b3a5e268b77f5887dded4054d5810fbe9937cc04/public/js/ktx2/msc_basis_transcoder.wasm -------------------------------------------------------------------------------- /public/js/ktx2/uastc_astc.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/lib-3d-scene-viewer/b3a5e268b77f5887dded4054d5810fbe9937cc04/public/js/ktx2/uastc_astc.wasm -------------------------------------------------------------------------------- /public/js/ktx2/uastc_bc7.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/lib-3d-scene-viewer/b3a5e268b77f5887dded4054d5810fbe9937cc04/public/js/ktx2/uastc_bc7.wasm -------------------------------------------------------------------------------- /public/js/ktx2/uastc_rgba8_srgb_v2.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/lib-3d-scene-viewer/b3a5e268b77f5887dded4054d5810fbe9937cc04/public/js/ktx2/uastc_rgba8_srgb_v2.wasm -------------------------------------------------------------------------------- /public/js/ktx2/uastc_rgba8_unorm_v2.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/lib-3d-scene-viewer/b3a5e268b77f5887dded4054d5810fbe9937cc04/public/js/ktx2/uastc_rgba8_unorm_v2.wasm -------------------------------------------------------------------------------- /public/js/ktx2/zstddec.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/lib-3d-scene-viewer/b3a5e268b77f5887dded4054d5810fbe9937cc04/public/js/ktx2/zstddec.wasm -------------------------------------------------------------------------------- /public/model/mannequin.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/lib-3d-scene-viewer/b3a5e268b77f5887dded4054d5810fbe9937cc04/public/model/mannequin.glb -------------------------------------------------------------------------------- /scripts/publish.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const { execSync } = require('child_process'); 18 | const fs = require('fs'); 19 | const path = require('path'); 20 | 21 | // Remove dist folder 22 | const distFolder = path.join(__dirname, '..', 'dist'); 23 | if (fs.existsSync(distFolder)) { 24 | fs.rmSync(distFolder, { recursive: true, force: true }); 25 | } 26 | 27 | // Install dependencies 28 | execSync('npm ci', { stdio: 'inherit' }); 29 | 30 | // Build and pack 31 | execSync('npm run pack:dist', { stdio: 'inherit' }); 32 | 33 | // Read package.json 34 | const packageJsonPath = path.join(distFolder, 'package.json'); 35 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); 36 | 37 | // Remove prepublishOnly script from package.json 38 | delete packageJson.scripts['prepublishOnly']; 39 | fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); 40 | 41 | // Publish 42 | execSync(`npm publish ${distFolder} --access public`, { stdio: 'inherit' }); 43 | -------------------------------------------------------------------------------- /scripts/updateBabylonjsVersion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const { execSync } = require('child_process'); 18 | const fs = require('fs'); 19 | 20 | // Get the version from command-line arguments 21 | const version = process.argv[2]; 22 | if (!version) { 23 | throw new Error('Please provide a version. For example: npm run update-bjs-ver -- 7.0.0'); 24 | } 25 | 26 | const dependencies = ['@babylonjs/core', '@babylonjs/gui', '@babylonjs/loaders', '@babylonjs/materials']; 27 | const devDependencies = ['@babylonjs/inspector']; 28 | 29 | // Run npm install with the given version 30 | for (const packageName of dependencies) { 31 | const packageToInstall = `${packageName}@${version}`; 32 | console.log(`Installing ${packageToInstall}`); 33 | execSync(`npm install ${packageToInstall} --save --save-exact`, { stdio: 'inherit' }); 34 | } 35 | 36 | for (const packageName of devDependencies) { 37 | const packageToInstall = `${packageName}@${version}`; 38 | console.log(`Installing ${packageToInstall}`); 39 | execSync(`npm install ${packageToInstall} --save-dev --save-exact`, { stdio: 'inherit' }); 40 | } 41 | 42 | // Read package.json 43 | const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); 44 | 45 | // Update peerDependencies with the installed version 46 | const currentVersion = packageJson.dependencies['@babylonjs/core']; 47 | const currentMajorVersion = parseInt(currentVersion.split('.')[0], 10); 48 | packageJson.peerDependencies = packageJson.peerDependencies ?? {}; 49 | for (const packageName of dependencies) { 50 | packageJson.peerDependencies[packageName] = `>= ${currentVersion} < ${currentMajorVersion + 1}`; 51 | fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2)); 52 | console.log(`Updated peerDependencies: ${packageName} to version ${currentVersion}`); 53 | } 54 | 55 | // Update package-lock.json 56 | console.log('Updating package-lock.json'); 57 | execSync('npm install', { stdio: 'inherit' }); 58 | -------------------------------------------------------------------------------- /src/camera/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from './camera'; 18 | -------------------------------------------------------------------------------- /src/config/cameraAnimationConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Animation } from '@babylonjs/core/Animations/animation'; 18 | import { 19 | BackEase, 20 | BezierCurveEase, 21 | BounceEase, 22 | CircleEase, 23 | CubicEase, 24 | ElasticEase, 25 | ExponentialEase, 26 | PowerEase, 27 | QuadraticEase, 28 | QuarticEase, 29 | QuinticEase, 30 | SineEase, 31 | } from '@babylonjs/core/Animations/easing'; 32 | 33 | /** 34 | * @see https://doc.babylonjs.com/typedoc/classes/BABYLON.Animation#ANIMATIONLOOPMODE_CONSTANT 35 | */ 36 | export type AnimationLoopMode = 'ANIMATIONLOOPMODE_RELATIVE' | 'ANIMATIONLOOPMODE_CYCLE' | 'ANIMATIONLOOPMODE_CONSTANT'; 37 | 38 | /** 39 | * @see https://doc.babylonjs.com/typedoc/classes/BABYLON.EasingFunction#EASINGMODE_EASEIN 40 | */ 41 | export type EasingMode = 'EASINGMODE_EASEIN' | 'EASINGMODE_EASEOUT' | 'EASINGMODE_EASEINOUT'; 42 | 43 | /** 44 | * @see https://doc.babylonjs.com/typedoc/classes/BABYLON.EasingFunction 45 | */ 46 | export type EasingFunction = 47 | | CircleEase 48 | | BackEase 49 | | BounceEase 50 | | CubicEase 51 | | ElasticEase 52 | | ExponentialEase 53 | | PowerEase 54 | | QuadraticEase 55 | | QuarticEase 56 | | QuinticEase 57 | | SineEase 58 | | BezierCurveEase; 59 | 60 | /** 61 | * Camera animatable property type 62 | */ 63 | export type CAMERA_ANIMATABLE_PROPERTY = 'fov' | 'target' | 'alpha' | 'beta' | 'radius'; 64 | 65 | /** 66 | * Mapping of the type of change for each animatable property 67 | */ 68 | export const CAMERA_ANIMATION_TYPE_MAP: Record = { 69 | fov: Animation.ANIMATIONTYPE_FLOAT, 70 | target: Animation.ANIMATIONTYPE_VECTOR3, 71 | alpha: Animation.ANIMATIONTYPE_FLOAT, 72 | beta: Animation.ANIMATIONTYPE_FLOAT, 73 | radius: Animation.ANIMATIONTYPE_FLOAT, 74 | }; 75 | 76 | /** 77 | * The Camera Animation Configuration groups the different settings used to define the camera animation behavior 78 | */ 79 | export interface CameraAnimationConfig { 80 | /** 81 | * A custom mathematical formula for animation 82 | * @default {@link https://doc.babylonjs.com/typedoc/classes/BABYLON.CubicEase CubicEase} 83 | */ 84 | easingFunction: EasingFunction; 85 | 86 | /** 87 | * The easing mode of the easing function 88 | * @default {@link https://doc.babylonjs.com/typedoc/classes/BABYLON.EasingFunction#EASINGMODE_EASEINOUT EASINGMODE_EASEINOUT} 89 | */ 90 | easingMode: EasingMode; 91 | 92 | /** 93 | * The loop mode of the animation 94 | * @default {@link https://doc.babylonjs.com/typedoc/classes/BABYLON.Animation#ANIMATIONLOOPMODE_CONSTANT ANIMATIONLOOPMODE_CONSTANT} 95 | */ 96 | loopMode: AnimationLoopMode; 97 | 98 | /** 99 | * The frames per second of the animation 100 | * @default 60 101 | */ 102 | maxFPS: number; 103 | } 104 | 105 | /** 106 | * Default camera animation configuration 107 | */ 108 | export const DEFAULT_CAMERA_ANIMATION_CONFIG: CameraAnimationConfig = { 109 | easingFunction: new CubicEase(), 110 | easingMode: 'EASINGMODE_EASEINOUT', 111 | loopMode: 'ANIMATIONLOOPMODE_CONSTANT', 112 | maxFPS: 60, 113 | }; 114 | -------------------------------------------------------------------------------- /src/config/cameraConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Vector3 } from '@babylonjs/core/Maths/math.vector'; 18 | 19 | import { CameraAnimationConfig } from './cameraAnimationConfig'; 20 | 21 | /** 22 | * A list of camera property that does not belong to 23 | * animatable property, special animatable property, and non-animatable property 24 | */ 25 | export const CAMERA_PROPERTIES_TO_IGNORE_DURING_EXTRACTION: Array = [ 26 | 'attachControl', 27 | 'autoRotationBehavior', 28 | 'cameraAnimationConfig', 29 | 'enable', 30 | 'framingBehavior', 31 | 'type', 32 | 'minimizeRotation', 33 | ]; 34 | 35 | /** 36 | * Arc rotate camera limit property type 37 | */ 38 | export type ArcRotateCameraLimitProperty = 39 | | 'lowerAlphaLimit' 40 | | 'upperAlphaLimit' 41 | | 'lowerBetaLimit' 42 | | 'upperBetaLimit' 43 | | 'lowerRadiusLimit' 44 | | 'upperRadiusLimit'; 45 | 46 | /** 47 | * A list of arc rotate camera limit property 48 | */ 49 | export const ARC_ROTATE_CAMERA_LIMIT_PROPERTIES: Array = [ 50 | 'lowerAlphaLimit', 51 | 'upperAlphaLimit', 52 | 'lowerBetaLimit', 53 | 'upperBetaLimit', 54 | 'lowerRadiusLimit', 55 | 'upperRadiusLimit', 56 | ]; 57 | 58 | /** 59 | * Arc rotate camera non animatable property type 60 | */ 61 | export type ArcRotateCameraNonAnimatableProperty = 62 | | ArcRotateCameraLimitProperty 63 | | 'minZ' 64 | | 'maxZ' 65 | | 'wheelPrecision' 66 | | 'pinchPrecision' 67 | | 'wheelDeltaPercentage' 68 | | 'pinchDeltaPercentage' 69 | | 'angularSensibilityX' 70 | | 'angularSensibilityY' 71 | | 'panningSensibility' 72 | | 'speed' 73 | | 'panningInertia' 74 | | 'inertia'; 75 | 76 | /** 77 | * Base properties of all types of cameras 78 | */ 79 | export interface CameraBaseProperty { 80 | /** 81 | * The amount of time (in seconds) to carry out the animation. 0 means no animation enabled. 82 | * @default 0 83 | */ 84 | animationDuration?: number; 85 | 86 | /** 87 | * Minimize the amount of rotation animation on arcCamera's 'alpha' and 'beta' keys. 88 | * 89 | * For alpha and beta values, we want to minimize the amount of rotation in the animation. 90 | * User interactions can cause the alpha and beta to be arbitrarily large or small and thus 91 | * cause the camera to spin around an unpredictable amount to reach the desired alpha and beta. 92 | * We can avoid this by animating to the current alpha(or beta) + the delta angle between the 93 | * current and desired alphas and betas 94 | * @default false 95 | */ 96 | minimizeRotation?: boolean; 97 | 98 | /** 99 | * The Camera Animation Configuration groups the different settings used to define the camera animation behavior 100 | * @default {@link config/cameraAnimationConfig.DEFAULT_CAMERA_ANIMATION_CONFIG} 101 | */ 102 | cameraAnimationConfig?: CameraAnimationConfig; 103 | 104 | /** 105 | * Whether to attach the input controls to a specific dom element to get the input from 106 | * @default true 107 | */ 108 | attachControl?: boolean; 109 | 110 | /** 111 | * Whether to enable the camera 112 | * @default true 113 | */ 114 | enable?: boolean; 115 | 116 | /** 117 | * Field Of View set in Degrees 118 | * @default 45.84 (i.e. 0.8 in Radians) 119 | */ 120 | fovInDegrees?: number; 121 | 122 | /** 123 | * Define the overall inertia of the camera. 124 | * This helps to give a smooth feeling to the camera movement. 125 | * @default 0.9 126 | */ 127 | inertia?: number; 128 | 129 | /** 130 | * Define the minimum distance the camera can see from 131 | * @default: 1 132 | */ 133 | minZ?: number; 134 | 135 | /** 136 | * Define the maximum distance the camera can see to 137 | * @default 10000 138 | */ 139 | maxZ?: number; 140 | 141 | /** 142 | * Define the current speed of the camera 143 | * @default 2 144 | */ 145 | speed?: number; 146 | 147 | /** 148 | * Defines the target point of the camera. 149 | * The camera looks towards it form the radius distance. 150 | * @default Vector3.Zero 151 | */ 152 | target?: Vector3; 153 | } 154 | 155 | /** 156 | * Arc rotate camera properties 157 | */ 158 | export interface ArcRotateCameraProperty extends CameraBaseProperty { 159 | /** 160 | * Type must be 'arcRotateCamera' 161 | */ 162 | type: 'arcRotateCamera'; 163 | 164 | /** 165 | * Defines the rotation angle of the camera along the longitudinal axis in Degrees 166 | * @default 0 167 | */ 168 | alphaInDegrees?: number; 169 | 170 | /** 171 | * Defines a smooth rotation of an ArcRotateCamera when there is no user interaction 172 | */ 173 | autoRotationBehavior?: { 174 | /** 175 | * Whether to enable ArcRotation camera's autoRotation behavior 176 | * @default false 177 | */ 178 | enabled?: boolean; 179 | 180 | /** 181 | * Speed at which the camera rotates around the mesh 182 | * @default 1 183 | */ 184 | idleRotationSpeed?: number; 185 | 186 | /** 187 | * Time (in milliseconds) to wait after user interaction before the camera starts rotating 188 | * @default 100 189 | */ 190 | idleRotationWaitTime?: number; 191 | 192 | /** 193 | * Time (milliseconds) to take to spin up to the full idle rotation speed 194 | * @default 100 195 | */ 196 | idleRotationSpinupTime?: number; 197 | 198 | /** 199 | * Flag that indicates if user zooming should stop animation 200 | * @default false 201 | */ 202 | zoomStopsAnimation?: boolean; 203 | }; 204 | 205 | /** 206 | * Defines framingBehavior of an ArcRotateCamera. 207 | * Must be configured before loading models. 208 | */ 209 | framingBehavior?: { 210 | /** 211 | * Whether to enable ArcRotation camera's framing behavior 212 | * @default false 213 | */ 214 | enabled?: boolean; 215 | 216 | /** 217 | * Define the transition time when framing the mesh, in milliseconds 218 | * @default 0 219 | */ 220 | framingTime?: number; 221 | 222 | /** 223 | * Define the scale applied to the radius 224 | * @default 1 225 | */ 226 | radiusScale?: number; 227 | }; 228 | 229 | /** 230 | * Minimum allowed angle on the longitudinal axis in Degrees. 231 | * This can help limiting how the Camera is able to move in the scene. 232 | * @default 0 233 | */ 234 | lowerAlphaLimitInDegrees?: number; 235 | 236 | /** 237 | * Maximum allowed angle on the longitudinal axis in Degrees. 238 | * This can help limiting how the Camera is able to move in the scene. 239 | * @default 0 240 | */ 241 | upperAlphaLimitInDegrees?: number; 242 | 243 | /** 244 | * Defines the rotation angle of the camera along the latitudinal axis in Degrees 245 | * @default 0 246 | */ 247 | betaInDegrees?: number; 248 | 249 | /** 250 | * Minimum allowed angle on the latitudinal axis in Degrees. 251 | * This can help limiting how the Camera is able to move in the scene. 252 | * @default 0.573 (i.e. 0.01 in Radians) 253 | */ 254 | lowerBetaLimitInDegrees?: number; 255 | 256 | /** 257 | * Maximum allowed angle on the latitudinal axis in Degrees. 258 | * This can help limiting how the Camera is able to move in the scene. 259 | * @default 179.6 (i.e. 3.1316 in Radians) 260 | */ 261 | upperBetaLimitInDegrees?: number; 262 | 263 | /** 264 | * Defines the radius of the camera from it s target point 265 | * @default 1 266 | */ 267 | radius?: number; 268 | 269 | /** 270 | * Minimum allowed distance of the camera to the target (The camera can not get closer). 271 | * This can help limiting how the Camera is able to move in the scene. 272 | * @default 0 273 | */ 274 | lowerRadiusLimit?: number; 275 | 276 | /** 277 | * Maximum allowed distance of the camera to the target (The camera can not get further). 278 | * This can help limiting how the Camera is able to move in the scene. 279 | * @default 0 280 | */ 281 | upperRadiusLimit?: number; 282 | 283 | /** 284 | * Control how fast is the camera zooming. The lower, the faster. 285 | * @default 3 286 | */ 287 | wheelPrecision?: number; 288 | 289 | /** 290 | * Control how fast is the camera zooming. The lower, the faster. 291 | * @default 12 292 | */ 293 | pinchPrecision?: number; 294 | 295 | /** 296 | * Control how fast is the camera zooming. The higher, the faster. 297 | * It will be used instead of wheelPrecision if different from 0. 298 | * It defines the percentage of current {@link radius} to use as delta when wheel zoom is used. 299 | * @default 0 300 | */ 301 | wheelDeltaPercentage?: number; 302 | 303 | /** 304 | * Control how fast is the camera zooming. The higher, the faster. 305 | * It will be used instead of pinchPrecision if different from 0. 306 | * It defines the percentage of current {@link radius} to use as delta when pinch zoom is used. 307 | * @default 0 308 | */ 309 | pinchDeltaPercentage?: number; 310 | 311 | /** 312 | * Control the pointer angular sensibility along the X axis or how fast is the camera rotating 313 | * @default 1000 314 | */ 315 | angularSensibilityX?: number; 316 | 317 | /** 318 | * Control the pointer angular sensibility along the Y axis or how fast is the camera rotating 319 | * @default 1000 320 | */ 321 | angularSensibilityY?: number; 322 | 323 | /** 324 | * Control the pointer panning sensibility or how fast is the camera moving 325 | * @default 1000 326 | */ 327 | panningSensibility?: number; 328 | 329 | /** 330 | * Defines the value of the inertia used during panning. 331 | * 0 would mean stop inertia and one would mean no deceleration at all. 332 | * @default 0.9 333 | */ 334 | panningInertia?: number; 335 | } 336 | 337 | /** 338 | * A super set of different kinds of camera property 339 | */ 340 | export type CameraProperty = ArcRotateCameraProperty; 341 | 342 | /** 343 | * The Camera Configuration groups the different settings of cameras. 344 | * 345 | * Each camera is defined in key-value pair, where key is the camera name and value is {@link CameraProperty}. 346 | * 347 | * Note: Key must be unique 348 | */ 349 | export interface CameraConfig { 350 | [cameraName: string]: CameraProperty; 351 | } 352 | -------------------------------------------------------------------------------- /src/config/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { EngineOptions } from '@babylonjs/core/Engines/thinEngine'; 18 | 19 | import { CameraConfig } from './cameraConfig'; 20 | import { ExtensionConfig } from './extensionConfig'; 21 | import { LightingConfig } from './lightingConfig'; 22 | import { RenderingPipelineConfig } from './renderingPipelineConfig'; 23 | import { SceneConfig } from './sceneConfig'; 24 | 25 | /** 26 | * Hold all configurable parameters for the viewer 27 | */ 28 | export interface Config { 29 | /** 30 | * Whether to enable 3D Commerce Certified Viewer 31 | * @see https://doc.babylonjs.com/setup/support/3D_commerce_certif 32 | * @default true 33 | */ 34 | '3dCommerceCertified'?: boolean; 35 | 36 | /** 37 | * Basis transcoder configuration 38 | * @see https://doc.babylonjs.com/typedoc/modules/BABYLON#BasisToolsOptions 39 | */ 40 | basisTranscoder?: { 41 | /** 42 | * Define where to find transcoder files. It can be an external URL or a local file path. 43 | * @default empty string for all urls 44 | */ 45 | urlConfig?: { 46 | jsModuleUrl?: string; 47 | wasmModuleUrl?: string; 48 | }; 49 | }; 50 | 51 | /** 52 | * Camera configuration 53 | * @default create an ArcRotateCamera with alpha = 0, beta = 0, radius = 1, and target = Vector3.Zero 54 | */ 55 | cameraConfig?: CameraConfig; 56 | 57 | /** 58 | * Draco compression configuration 59 | * @see https://doc.babylonjs.com/typedoc/classes/BABYLON.DracoCompression 60 | */ 61 | dracoCompression?: { 62 | /** 63 | * Define where to find decoders. It can be an external URL or a local file path. 64 | * @default empty string for all urls 65 | */ 66 | decoders?: { 67 | wasmUrl?: string; 68 | wasmBinaryUrl?: string; 69 | fallbackUrl?: string; 70 | }; 71 | 72 | /** 73 | * Default number of workers to create when creating the draco compression object 74 | * @default defer to babylon.js 75 | */ 76 | defaultNumWorkers?: number; 77 | }; 78 | 79 | /** 80 | * Extension configuration 81 | */ 82 | extensionConfig?: ExtensionConfig; 83 | 84 | /** 85 | * Whether to enable drag and drop feature. 86 | * When enabled, users are able to drag and drop their local model files to the viewer. 87 | * @default false 88 | */ 89 | enableDragAndDrop?: boolean; 90 | 91 | /** 92 | * Engine configuration 93 | */ 94 | engineConfig?: { 95 | /** 96 | * Whether to enable antialiasing 97 | * @default true 98 | */ 99 | antialiasing?: boolean; 100 | 101 | /** 102 | * Whether to disable handling 'resize' event 103 | * @default false 104 | */ 105 | disableResize?: boolean; 106 | 107 | /** 108 | * Interface defining initialization parameters for Engine class 109 | */ 110 | engineOptions?: EngineOptions; 111 | 112 | /** 113 | * Whether to use NullEngine which provides support for headless version of babylon.js. 114 | * This can be used in server side scenario or for testing purposes. 115 | * @see https://doc.babylonjs.com/typedoc/classes/BABYLON.NullEngine 116 | * @default false 117 | */ 118 | useNullEngine?: boolean; 119 | }; 120 | 121 | /** 122 | * Khronos Texture Extension 2 (KTX2) decoder configuration 123 | * @see https://doc.babylonjs.com/typedoc/classes/BABYLON.KhronosTextureContainer2 124 | */ 125 | ktx2Decoder?: { 126 | /** 127 | * Define where to find decoders. It can be an external URL or a local file path. 128 | * @default empty string or null for all urls 129 | */ 130 | urlConfig?: { 131 | jsDecoderModule: string; 132 | jsMSCTranscoder: string | null; 133 | wasmMSCTranscoder: string | null; 134 | wasmUASTCToASTC: string | null; 135 | wasmUASTCToBC7: string | null; 136 | wasmUASTCToR8_UNORM: string | null; 137 | wasmUASTCToRG8_UNORM: string | null; 138 | wasmUASTCToRGBA_SRGB: string | null; 139 | wasmUASTCToRGBA_UNORM: string | null; 140 | wasmZSTDDecoder: string | null; 141 | }; 142 | 143 | /** 144 | * Default number of workers used to handle data decoding 145 | * @default defer to babylon.js 146 | */ 147 | defaultNumWorkers?: number; 148 | }; 149 | 150 | /** 151 | * Lighting configuration 152 | */ 153 | lightingConfig?: LightingConfig; 154 | 155 | /** 156 | * Meshopt compression configuration 157 | * @see https://doc.babylonjs.com/typedoc/classes/BABYLON.MeshoptCompression 158 | */ 159 | meshoptCompression?: { 160 | /** 161 | * Define where to find decoder file. It can be an external URL or a local file path. 162 | * @default empty string for all urls 163 | */ 164 | decoder?: { 165 | url: string; 166 | }; 167 | }; 168 | 169 | /** 170 | * Rendering pipeline configuration 171 | */ 172 | renderingPipelineConfig?: RenderingPipelineConfig; 173 | 174 | /** 175 | * Scene configuration 176 | */ 177 | sceneConfig?: SceneConfig; 178 | } 179 | -------------------------------------------------------------------------------- /src/config/extensionConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export type ExtensionConfig = object; 18 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from './cameraAnimationConfig'; 18 | export * from './cameraConfig'; 19 | export * from './config'; 20 | export * from './extensionConfig'; 21 | export * from './lightingConfig'; 22 | export * from './preset'; 23 | export * from './renderingPipelineConfig'; 24 | export * from './sceneConfig'; 25 | -------------------------------------------------------------------------------- /src/config/lightingConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Color3 } from '@babylonjs/core/Maths/math.color'; 18 | import { Matrix, Vector3 } from '@babylonjs/core/Maths/math.vector'; 19 | 20 | /** 21 | * Base properties of all types of lighting 22 | */ 23 | export interface LightingBaseProperty { 24 | /** 25 | * Whether to enable the light 26 | * @default true 27 | */ 28 | enable?: boolean; 29 | 30 | /** 31 | * Strength of the light 32 | * @default 1 33 | */ 34 | intensity?: number; 35 | } 36 | 37 | /** 38 | * Environment texture properties 39 | */ 40 | export interface EnvironmentProperty extends LightingBaseProperty { 41 | /** 42 | * Local file path or URL of the texture map 43 | */ 44 | filePath?: string; 45 | 46 | /** 47 | * Define if the texture contains data in gamma space (most of the png/jpg aside bump). 48 | * HDR texture are usually stored in linear space. 49 | * @default true 50 | */ 51 | gammaSpace?: boolean; 52 | 53 | /** 54 | * The cubemap desired size (the more it increases the longer the generation will be). 55 | * Only for .hdr file. 56 | * @default 128 57 | */ 58 | size?: number; 59 | 60 | /** 61 | * Texture matrix includes the requested offsetting, tiling and rotation components 62 | * @default Matrix.Identity() 63 | */ 64 | textureMatrix?: Matrix; 65 | 66 | /** 67 | * File extension, either '.hdr' or '.env' 68 | */ 69 | type: 'hdr' | 'env'; 70 | } 71 | 72 | /** 73 | * Hemispheric (ambient) light properties 74 | */ 75 | export interface AmbientLightProperty extends LightingBaseProperty { 76 | /** 77 | * The light reflection direction, not the incoming direction 78 | * @default Vector3(0, 0, -1) 79 | */ 80 | direction?: Vector3; 81 | 82 | /** 83 | * Diffuse gives the basic color to an object 84 | * @default Color3.White 85 | */ 86 | diffuseColor?: Color3; 87 | 88 | /** 89 | * Specular produces a highlight color on an object 90 | * 91 | * Note: This is not affecting PBR materials 92 | * @default Color3.Black 93 | */ 94 | specularColor?: Color3; 95 | 96 | /** 97 | * Type of light, must be 'ambient' 98 | */ 99 | type: 'ambient'; 100 | } 101 | 102 | /** 103 | * Directional light properties 104 | */ 105 | export interface DirectionalLightProperty extends LightingBaseProperty { 106 | /** 107 | * The light direction of directional light 108 | * @default Vector3(0, 0, -1) 109 | */ 110 | direction?: Vector3; 111 | 112 | /** 113 | * Diffuse gives the basic color to an object 114 | * @default Color3.White 115 | */ 116 | diffuseColor?: Color3; 117 | 118 | /** 119 | * Specular produces a highlight color on an object 120 | * 121 | * Note: This is not affecting PBR materials 122 | * @default Color3.White 123 | */ 124 | specularColor?: Color3; 125 | 126 | /** 127 | * Type of light, must be 'directional' 128 | */ 129 | type: 'directional'; 130 | } 131 | 132 | /** 133 | * A super set of different kinds of light property 134 | */ 135 | export type LightingProperty = EnvironmentProperty | AmbientLightProperty | DirectionalLightProperty; 136 | 137 | /** 138 | * The Lighting Configuration groups the different settings of lights. 139 | * 140 | * Each light is defined in key-value pair, where key is the light name and value is {@link LightingProperty}. 141 | * 142 | * Note: Key must be unique 143 | */ 144 | export interface LightingConfig { 145 | [lightingName: string]: LightingProperty; 146 | } 147 | -------------------------------------------------------------------------------- /src/config/preset/defaultConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Color3, Color4 } from '@babylonjs/core/Maths/math.color'; 18 | import { Vector3 } from '@babylonjs/core/Maths/math.vector'; 19 | 20 | import { CameraConfig } from '../cameraConfig'; 21 | import { Config } from '../config'; 22 | import { LightingConfig } from '../lightingConfig'; 23 | 24 | export const DEFAULT_LIGHTING: LightingConfig = { 25 | Neutral: { 26 | type: 'env', 27 | enable: true, 28 | filePath: 'public/ibl/Neutral.env', 29 | gammaSpace: false, 30 | intensity: 2.4, 31 | }, 32 | }; 33 | 34 | export const DEFAULT_CAMERA_CONFIG: CameraConfig = { 35 | ArcRotateCamera: { 36 | type: 'arcRotateCamera', 37 | attachControl: true, 38 | enable: true, 39 | target: Vector3.Zero(), 40 | alphaInDegrees: 90, 41 | betaInDegrees: 90, 42 | radius: 1, 43 | wheelPrecision: 1000, 44 | pinchPrecision: 1000, 45 | angularSensibilityX: 2000, 46 | angularSensibilityY: 2000, 47 | panningSensibility: 3000, 48 | lowerRadiusLimit: 0.4, 49 | upperRadiusLimit: 10, 50 | inertia: 0.9, 51 | fovInDegrees: 45, 52 | minZ: 0.1, 53 | maxZ: 100, 54 | animationDuration: 0, 55 | lowerBetaLimitInDegrees: 0.01, 56 | upperBetaLimitInDegrees: 179.99, 57 | }, 58 | }; 59 | 60 | export const DEFAULT_CONFIG: Config = { 61 | '3dCommerceCertified': true, 62 | 63 | cameraConfig: DEFAULT_CAMERA_CONFIG, 64 | 65 | enableDragAndDrop: true, 66 | 67 | engineConfig: { 68 | antialiasing: true, 69 | disableResize: false, 70 | engineOptions: { 71 | disableWebGL2Support: false, 72 | }, 73 | }, 74 | 75 | lightingConfig: DEFAULT_LIGHTING, 76 | 77 | sceneConfig: { 78 | clearColor: Color4.FromColor3(Color3.White()), 79 | }, 80 | }; 81 | -------------------------------------------------------------------------------- /src/config/preset/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from './defaultConfig'; 18 | export * from './v3dConfig'; 19 | -------------------------------------------------------------------------------- /src/config/preset/v3dConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Color3, Color4 } from '@babylonjs/core/Maths/math.color'; 18 | import { Matrix, Vector3 } from '@babylonjs/core/Maths/math.vector'; 19 | import { Tools } from '@babylonjs/core/Misc/tools'; 20 | 21 | import { CameraConfig } from '../cameraConfig'; 22 | import { Config } from '../config'; 23 | import { LightingConfig } from '../lightingConfig'; 24 | 25 | export const V3D_LIGHTING: LightingConfig = { 26 | DirectionalBlur: { 27 | type: 'env', 28 | enable: true, 29 | gammaSpace: false, 30 | filePath: 'public/ibl/Directional_colorVariationOnTopBlur100_512.env', 31 | intensity: 1.6, 32 | textureMatrix: Matrix.RotationY(Tools.ToRadians(344)), 33 | }, 34 | }; 35 | 36 | export const V3D_DEFAULT_CAMERA_CONFIG: CameraConfig = { 37 | ArcRotateCamera: { 38 | type: 'arcRotateCamera', 39 | attachControl: true, 40 | enable: true, 41 | target: Vector3.Zero(), 42 | alphaInDegrees: Tools.ToDegrees(2.5), 43 | betaInDegrees: Tools.ToDegrees(0.975), 44 | radius: 0.8, 45 | wheelPrecision: 1000, 46 | pinchPrecision: 800, 47 | angularSensibilityX: 850, 48 | angularSensibilityY: 850, 49 | panningSensibility: 0, 50 | lowerRadiusLimit: 0.4, 51 | upperRadiusLimit: 1.2, 52 | inertia: 0.85, 53 | fovInDegrees: 45, 54 | minZ: 0.1, 55 | maxZ: 100, 56 | }, 57 | }; 58 | 59 | export const V3D_CONFIG: Config = { 60 | '3dCommerceCertified': true, 61 | 62 | basisTranscoder: { 63 | urlConfig: { 64 | jsModuleUrl: 'public/js/basis/basis_transcoder.js', 65 | wasmModuleUrl: 'public/js/basis/basis_transcoder.wasm', 66 | }, 67 | }, 68 | 69 | cameraConfig: V3D_DEFAULT_CAMERA_CONFIG, 70 | 71 | dracoCompression: { 72 | decoders: { 73 | wasmBinaryUrl: 'public/js/draco/draco_decoder_gltf.wasm', 74 | wasmUrl: 'public/js/draco/draco_decoder_gltf_nodejs.js', 75 | fallbackUrl: 'public/js/draco/draco_decoder_gltf.js', 76 | }, 77 | }, 78 | 79 | enableDragAndDrop: false, 80 | 81 | engineConfig: { 82 | antialiasing: true, 83 | disableResize: false, 84 | engineOptions: { 85 | disableWebGL2Support: false, 86 | }, 87 | }, 88 | 89 | ktx2Decoder: { 90 | urlConfig: { 91 | jsDecoderModule: 'public/js/ktx2/babylon.ktx2Decoder.js', 92 | jsMSCTranscoder: 'public/js/ktx2/msc_basis_transcoder.js', 93 | wasmMSCTranscoder: 'public/js/ktx2/msc_basis_transcoder.wasm', 94 | wasmUASTCToASTC: 'public/js/ktx2/uastc_astc.wasm', 95 | wasmUASTCToBC7: 'public/js/ktx2/uastc_bc7.wasm', 96 | wasmUASTCToR8_UNORM: null, 97 | wasmUASTCToRG8_UNORM: null, 98 | wasmUASTCToRGBA_SRGB: 'public/js/ktx2/uastc_rgba8_srgb_v2.wasm', 99 | wasmUASTCToRGBA_UNORM: 'public/js/ktx2/uastc_rgba8_unorm_v2.wasm', 100 | wasmZSTDDecoder: 'public/js/ktx2/zstddec.wasm', 101 | }, 102 | }, 103 | 104 | lightingConfig: V3D_LIGHTING, 105 | 106 | meshoptCompression: { 107 | decoder: { 108 | url: 'public/js/meshopt/meshopt_decoder.js', 109 | }, 110 | }, 111 | 112 | renderingPipelineConfig: { 113 | defaultRenderingPipeline: { 114 | enable: true, 115 | fxaaEnabled: true, 116 | samples: 8, 117 | imageProcessing: { 118 | enable: true, 119 | contrast: 1, 120 | exposure: 1, 121 | toneMappingEnabled: true, 122 | toneMappingType: 2, 123 | }, 124 | }, 125 | }, 126 | 127 | sceneConfig: { 128 | clearColor: Color4.FromColor3(new Color3(0.95, 0.95, 0.95)), 129 | useLoadingUI: false, 130 | }, 131 | }; 132 | -------------------------------------------------------------------------------- /src/config/renderingPipelineConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Define different types of rendering pipeline 19 | */ 20 | export interface RenderingPipelineConfig { 21 | /** 22 | * Default rendering pipeline configuration. 23 | * 24 | * The default rendering pipeline can be added to a scene to apply common 25 | * post-processing effects such as anti-aliasing or depth of field. 26 | */ 27 | defaultRenderingPipeline?: { 28 | /** 29 | * Whether to enable the rendering pipeline 30 | * @default true 31 | */ 32 | enable?: boolean; 33 | 34 | /** 35 | * Image post-processing pass used to perform operations 36 | */ 37 | imageProcessing?: { 38 | /** 39 | * Whether to enable image post-processing 40 | * @default true 41 | */ 42 | enable?: boolean; 43 | 44 | /** 45 | * Contrast used in the effect 46 | * @default 1 47 | */ 48 | contrast?: number; 49 | 50 | /** 51 | * Exposure used in the effect 52 | * @default 1 53 | */ 54 | exposure?: number; 55 | 56 | /** 57 | * Enable tone mapping 58 | * @default false 59 | */ 60 | toneMappingEnabled?: boolean; 61 | 62 | /** 63 | * Tone mapping type 64 | * 0: Standard 65 | * 1: ACES 66 | * 2: Khronos PBR Neutral 67 | * @default 0 68 | */ 69 | toneMappingType?: 0 | 1 | 2; 70 | }; 71 | 72 | /** 73 | * MSAA sample count, setting this to 4 will provide 4x anti aliasing 74 | * @default 1 75 | */ 76 | samples?: number; 77 | 78 | /** 79 | * Fast Approximate Anti-aliasing (FXAA) uses a full screen pass that smooths 80 | * edges on a per-pixel level. 81 | * @default false 82 | */ 83 | fxaaEnabled?: boolean; 84 | }; 85 | 86 | /** 87 | * SSAO2 rendering pipeline configuration. 88 | * 89 | * This rendering pipeline can be added to a scene to apply 90 | * post-processing effects such as Squared Space Ambient Occlusion (SSAO). 91 | * Note: only available with WebGL2. 92 | */ 93 | ssao2RenderingPipeline?: { 94 | /** 95 | * Whether to enable the rendering pipeline. 96 | * @default false 97 | */ 98 | enable?: boolean; 99 | 100 | /** 101 | * The size of the post-processes is a number shared between passes or for more 102 | * precision modify ssaoRatio, blurRatio, and combineRatio. 103 | * @default 0.5 104 | */ 105 | ratio?: number; 106 | 107 | /** 108 | * ratio of the SSAO post-process used. Is more specific than the generic ratio placeholder 109 | * Note: all 3 ssaoRatio & blurRatio & combineRatio must be configured for this to apply. 110 | * @default undefined 111 | */ 112 | ssaoRatio?: number; 113 | 114 | /** 115 | * A horizontal and vertical Gaussian shader blur to clear the noise. 116 | * Note: all 3 ssaoRatio & blurRatio & combineRatio must be configured for this to apply. 117 | * @default undefined 118 | */ 119 | blurRatio?: number; 120 | 121 | /** 122 | * Ratio of the combine post-process (combines the SSAO and the scene. 123 | * Note: all 3 ssaoRatio & blurRatio & combineRatio must be configured for this to apply. 124 | * @default undefined 125 | */ 126 | combineRatio?: number; 127 | 128 | /** 129 | * A legacy geometry buffer renderer. 130 | * @default false 131 | */ 132 | forceGeometryBuffer?: boolean; 133 | 134 | /** 135 | * Number of samples to use for antialiasing, setting this to 4 will provide 4x anti aliasing. 136 | * @default 1 137 | */ 138 | textureSamples?: number; 139 | 140 | /** 141 | * Number of samples used for the SSAO calculations. 142 | * @default 1 143 | */ 144 | samples?: number; 145 | 146 | /** 147 | * Enables the configurable bilateral de-noising (blurring) filter. 148 | * Set false to instead use a legacy bilateral filter that can't be configured. 149 | * @default true 150 | */ 151 | expensiveBlur?: boolean; 152 | }; 153 | } 154 | -------------------------------------------------------------------------------- /src/config/sceneConfig.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Color3, Color4 } from '@babylonjs/core/Maths/math.color'; 18 | 19 | /** 20 | * Scene configuration 21 | */ 22 | export interface SceneConfig { 23 | /** 24 | * Color of the bounding box lines placed in front of an object 25 | * @default '#EBA832' 26 | */ 27 | boundingBoxFrontColor?: Color3; 28 | 29 | /** 30 | * Color of the bounding box lines placed behind an object 31 | * @default Color3(1, 0, 1) 32 | */ 33 | boundingBoxBackColor?: Color3; 34 | 35 | /** 36 | * Defines the color used to clear the render buffer 37 | * @default Color4(1, 1, 1, 1) 38 | */ 39 | clearColor?: Color4; 40 | 41 | /** 42 | * Whether to display the loading UI when loading a model 43 | * @default false 44 | */ 45 | useLoadingUI?: boolean; 46 | 47 | /** 48 | * The logo url to use for the loading screen 49 | * @default 1 pixel (alpha = 0) base64 data 50 | */ 51 | loadingUILogoUrl?: string; 52 | 53 | /** 54 | * The spinner url to use for the loading screen 55 | * @default 1 pixel (alpha = 0) base64 data 56 | */ 57 | loadingUISpinnerUrl?: string; 58 | } 59 | -------------------------------------------------------------------------------- /src/dev/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * This file is only for testing/demo purposes 19 | */ 20 | 21 | function createButton(name: string, url: string) { 22 | const div = document.createElement('div'); 23 | const button = document.createElement('button'); 24 | button.innerText = name; 25 | button.onclick = () => { 26 | window.open(url, '_blank'); 27 | }; 28 | button.style.marginBottom = '5px'; 29 | div.appendChild(button); 30 | document.body.appendChild(div); 31 | } 32 | 33 | (function () { 34 | createButton('3D scene viewer', './v3dViewer.html'); 35 | createButton('API docs', './docs/index.html'); 36 | })(); 37 | -------------------------------------------------------------------------------- /src/dev/v3dViewer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { CubicEase } from '@babylonjs/core/Animations/easing'; 18 | import { Vector3 } from '@babylonjs/core/Maths/math.vector'; 19 | import { DracoCompression } from '@babylonjs/core/Meshes/Compression/dracoCompression'; 20 | import { Tools } from '@babylonjs/core/Misc/tools'; 21 | 22 | import { CameraConfig } from '../config/cameraConfig'; 23 | import { Config } from '../config/config'; 24 | import { V3D_CONFIG } from '../config/preset/v3dConfig'; 25 | import { AbstractScene } from '../scene/abstractScene'; 26 | import { V3DScene } from '../scene/v3dScene'; 27 | import './viewer.css'; 28 | 29 | function createCanvas(): HTMLCanvasElement { 30 | const canvas = document.createElement('canvas'); 31 | canvas.id = 'renderCanvas'; 32 | return canvas; 33 | } 34 | 35 | function createHelperText(): HTMLDivElement { 36 | const helperTextDiv = document.createElement('div'); 37 | helperTextDiv.id = 'helperTextDiv'; 38 | helperTextDiv.innerText = [ 39 | '- Drag and drop local GLB/GLTF files to view them', 40 | '- Press "SHIFT" and "?" to toggle the debug layer', 41 | ].join('\n'); 42 | return helperTextDiv; 43 | } 44 | 45 | /** 46 | * This file is only for testing/demo purposes 47 | */ 48 | (async function () { 49 | const canvas = createCanvas(); 50 | const helperTextDiv = createHelperText(); 51 | document.body.appendChild(canvas); 52 | document.body.appendChild(helperTextDiv); 53 | 54 | // --> Step 1: create an instance of V3DScene 55 | const v3dScene = new V3DScene(canvas, V3D_CONFIG, { 56 | enableDragAndDrop: true, 57 | sceneConfig: { 58 | useLoadingUI: true, 59 | }, 60 | }); 61 | 62 | // --> Step 2: register any observers using V3DScene.observableManager 63 | 64 | // Set camera first interaction callback 65 | v3dScene.setOnCameraFirstInteraction(() => { 66 | console.log('CameraFirstInteraction'); 67 | }); 68 | 69 | v3dScene.observableManager.onViewerInitDoneObservable.add((viewer: AbstractScene) => { 70 | if (!viewer) { 71 | return; 72 | } 73 | 74 | console.log(`HardwareScalingLevel: ${viewer.engine.getHardwareScalingLevel()}`); 75 | console.log(DracoCompression.Configuration); 76 | }); 77 | 78 | v3dScene.observableManager.onConfigChangedObservable.add((config: Config) => { 79 | console.log(config); 80 | }); 81 | 82 | v3dScene.observableManager.onModelLoadedObservable.add((model) => { 83 | console.log(`Bounding box dimensions: ${model.getOverallBoundingBoxDimensions()}`); 84 | 85 | model.showShadowOnGroundDepthMap(); 86 | model.moveCenterToTargetCoordinate(); 87 | 88 | const radius = 2 * Math.max(...model.getOverallBoundingBoxDimensions().asArray()); 89 | v3dScene.updateConfig({ 90 | cameraConfig: { 91 | ArcRotateCamera: { 92 | type: 'arcRotateCamera', 93 | radius: radius, 94 | lowerRadiusLimit: radius * 0.05, 95 | upperRadiusLimit: radius * 5, 96 | minZ: radius * 0.02, 97 | maxZ: radius * 40, 98 | }, 99 | }, 100 | }); 101 | }); 102 | 103 | // --> Step 3: call V3DScene.init() function 104 | await v3dScene.init(); 105 | 106 | // --> Step 4: load glTF/glb model 107 | const model = await v3dScene.loadGltf('public/model/mannequin.glb', true); 108 | 109 | // --> Step 5: call V3DScene.updateConfig() to update scene setup and/or handle user interactions 110 | const radius = 2 * Math.max(...model.getOverallBoundingBoxDimensions().asArray()); 111 | const alphaInDegrees = Tools.ToDegrees(2.5); 112 | const cameraConfigPre: CameraConfig = { 113 | ArcRotateCamera: { 114 | type: 'arcRotateCamera', 115 | enable: true, 116 | attachControl: true, 117 | target: Vector3.Zero(), 118 | alphaInDegrees: alphaInDegrees + 360, 119 | betaInDegrees: Tools.ToDegrees(0.975), 120 | lowerBetaLimitInDegrees: Tools.ToDegrees(0.01), 121 | upperBetaLimitInDegrees: Tools.ToDegrees(3.132), 122 | radius: radius, 123 | lowerRadiusLimit: radius * 0.05, 124 | upperRadiusLimit: radius * 5, 125 | minZ: radius * 0.02, 126 | maxZ: radius * 40, 127 | animationDuration: 0, 128 | minimizeRotation: false, 129 | }, 130 | }; 131 | 132 | const cameraConfig: CameraConfig = { 133 | ArcRotateCamera: { 134 | type: 'arcRotateCamera', 135 | alphaInDegrees: alphaInDegrees, 136 | animationDuration: 2, 137 | cameraAnimationConfig: { 138 | easingFunction: new CubicEase(), 139 | easingMode: 'EASINGMODE_EASEINOUT', 140 | loopMode: 'ANIMATIONLOOPMODE_CONSTANT', 141 | maxFPS: 60, 142 | }, 143 | }, 144 | }; 145 | 146 | await v3dScene.updateConfig({ 147 | cameraConfig: cameraConfigPre, 148 | }); 149 | await v3dScene.updateConfig({ 150 | cameraConfig: cameraConfig, 151 | }); 152 | 153 | // Create keystroke tests 154 | document.addEventListener('keydown', async (event) => { 155 | const key = event.key; 156 | // Pressing '?' should show/hide the debug layer 157 | if (key === '?') { 158 | import('@babylonjs/inspector').then(() => { 159 | v3dScene.toggleDebugMode(); 160 | }); 161 | } 162 | }); 163 | })(); 164 | -------------------------------------------------------------------------------- /src/dev/viewer.css: -------------------------------------------------------------------------------- 1 | body, 2 | html { 3 | overflow: hidden; 4 | width: 100%; 5 | height: 100%; 6 | margin: 0; 7 | } 8 | 9 | canvas { 10 | touch-action: none; 11 | width: 100%; 12 | height: 100%; 13 | z-index: 10; 14 | } 15 | 16 | #helperTextDiv { 17 | position: absolute; 18 | top: 20px; 19 | left: 50%; 20 | transform: translateX(-50%); 21 | font-size: medium; 22 | font-family: "DejaVu Sans Mono for Powerline", monospace; 23 | white-space: pre-line; 24 | line-height: 1.5; 25 | } 26 | -------------------------------------------------------------------------------- /src/dev/viewer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Viewer 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from './camera/index'; 18 | export * from './config/index'; 19 | export * from './lighting/index'; 20 | export * from './manager/index'; 21 | export * from './material/index'; 22 | export * from './model/index'; 23 | export * from './scene/index'; 24 | export * from './util/index'; 25 | -------------------------------------------------------------------------------- /src/lighting/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from './lighting'; 18 | -------------------------------------------------------------------------------- /src/lighting/lighting.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import '@babylonjs/core/Materials/Textures/Loaders/envTextureLoader'; 18 | import { DirectionalLight } from '@babylonjs/core/Lights/directionalLight'; 19 | import { HemisphericLight } from '@babylonjs/core/Lights/hemisphericLight'; 20 | import { Light } from '@babylonjs/core/Lights/light'; 21 | import { BaseTexture } from '@babylonjs/core/Materials/Textures/baseTexture'; 22 | import { CubeTexture } from '@babylonjs/core/Materials/Textures/cubeTexture'; 23 | import { HDRCubeTexture } from '@babylonjs/core/Materials/Textures/hdrCubeTexture'; 24 | import { Color3 } from '@babylonjs/core/Maths/math.color'; 25 | import { Matrix, Vector3 } from '@babylonjs/core/Maths/math.vector'; 26 | import { IDisposable, Scene } from '@babylonjs/core/scene'; 27 | 28 | import { 29 | AmbientLightProperty, 30 | DirectionalLightProperty, 31 | EnvironmentProperty, 32 | LightingConfig, 33 | } from '../config/lightingConfig'; 34 | import { SceneManager } from '../manager/sceneManager'; 35 | 36 | /** 37 | * {@link Lighting} holds and manages all BabylonJS lights and scene environment defined in {@link LightingConfig} 38 | */ 39 | export class Lighting implements IDisposable { 40 | private readonly _scene: Scene; 41 | 42 | private _lights: Map; 43 | 44 | private _cubeTextures: Map; 45 | 46 | /** 47 | * Instantiate a new Lighting 48 | * @param sceneManager - an instance of {@link SceneManager} 49 | */ 50 | public constructor(sceneManager: SceneManager) { 51 | this._scene = sceneManager.scene; 52 | this._lights = new Map(); 53 | this._cubeTextures = new Map(); 54 | } 55 | 56 | /** 57 | * Create and/or update lights and environment in the scene based on {@link LightingConfig} 58 | * @param lightingConfig - an instance of {@link LightingConfig} that defines all lights and environment in the scene 59 | */ 60 | public async updateLighting(lightingConfig: LightingConfig): Promise { 61 | Object.entries(lightingConfig).forEach(([lightingName, lightingProperty]) => { 62 | lightingProperty.enable = lightingProperty.enable ?? true; 63 | 64 | switch (lightingProperty.type) { 65 | case 'hdr': 66 | case 'env': 67 | this._updateEnvironmentTexture(lightingProperty as EnvironmentProperty, lightingName); 68 | break; 69 | case 'ambient': 70 | this._updateAmbientLight(lightingProperty as AmbientLightProperty, lightingName); 71 | break; 72 | case 'directional': 73 | this._updateDirectionalLight(lightingProperty as DirectionalLightProperty, lightingName); 74 | break; 75 | } 76 | }); 77 | 78 | await this._scene.whenReadyAsync(); 79 | } 80 | 81 | /** 82 | * Destroy the lighting and release the current resources held by it 83 | */ 84 | public dispose(): void { 85 | this._lights.forEach((light: Light) => { 86 | light.dispose(); 87 | this._scene.removeLight(light); 88 | }); 89 | this._lights.clear(); 90 | 91 | this._cubeTextures.forEach((texture: BaseTexture) => { 92 | texture.dispose(); 93 | }); 94 | this._cubeTextures.clear(); 95 | 96 | this._scene.environmentTexture?.dispose(); 97 | this._scene.environmentTexture = null; 98 | } 99 | 100 | private _updateEnvironmentTexture(lightingProperty: EnvironmentProperty, lightingName: string): void { 101 | if (!lightingProperty.enable) { 102 | if (this._scene.environmentTexture?.name === lightingName) { 103 | this._scene.environmentTexture = null; 104 | } 105 | return; 106 | } 107 | 108 | if (!lightingProperty.filePath) { 109 | throw Error(`The filePath is empty or undefined for the lighting ${lightingName}`); 110 | } 111 | 112 | const key = `${lightingName}__${lightingProperty.filePath}`; 113 | let texture = this._cubeTextures.get(key); 114 | if (!texture) { 115 | if (lightingProperty.type === 'hdr') { 116 | texture = new HDRCubeTexture(lightingProperty.filePath, this._scene, lightingProperty.size ?? 128); 117 | } else { 118 | texture = CubeTexture.CreateFromPrefilteredData( 119 | lightingProperty.filePath, 120 | this._scene, 121 | `.${lightingProperty.type}`, 122 | ); 123 | } 124 | texture.name = lightingName; 125 | texture.gammaSpace = lightingProperty.gammaSpace ?? true; 126 | this._cubeTextures.set(key, texture); 127 | } 128 | 129 | const textureMatrix = lightingProperty.textureMatrix ?? Matrix.Identity(); 130 | if (texture instanceof HDRCubeTexture || texture instanceof CubeTexture) { 131 | texture.setReflectionTextureMatrix(textureMatrix); 132 | } 133 | 134 | this._scene.environmentTexture = texture; 135 | this._scene.environmentIntensity = lightingProperty.intensity ?? 1; 136 | } 137 | 138 | private _updateAmbientLight(lightingProperty: AmbientLightProperty, lightingName: string): void { 139 | let light = this._lights.get(lightingName); 140 | if (!light && lightingProperty.enable) { 141 | light = new HemisphericLight(lightingName, new Vector3(0, 0, -1), this._scene); 142 | this._lights.set(lightingName, light); 143 | } 144 | 145 | if (light) { 146 | const ambientLight = light as HemisphericLight; 147 | 148 | if (lightingProperty.enable) { 149 | ambientLight.direction = lightingProperty.direction ?? new Vector3(0, 0, -1); 150 | ambientLight.intensity = lightingProperty.intensity ?? 1; 151 | ambientLight.diffuse = lightingProperty.diffuseColor ?? Color3.White(); 152 | ambientLight.specular = lightingProperty.specularColor ?? Color3.Black(); 153 | } else { 154 | light.dispose(); 155 | this._scene.removeLight(light); 156 | this._lights.delete(light.name); 157 | } 158 | } 159 | } 160 | 161 | private _updateDirectionalLight(lightingProperty: DirectionalLightProperty, lightingName: string): void { 162 | let light = this._lights.get(lightingName); 163 | if (!light && lightingProperty.enable) { 164 | light = new DirectionalLight(lightingName, new Vector3(0, 0, -1), this._scene); 165 | this._lights.set(lightingName, light); 166 | } 167 | 168 | if (light) { 169 | const directionalLight = light as DirectionalLight; 170 | 171 | if (lightingProperty.enable) { 172 | directionalLight.direction = lightingProperty.direction ?? new Vector3(0, 0, -1); 173 | directionalLight.position = Vector3.Zero(); 174 | directionalLight.intensity = lightingProperty.intensity ?? 1; 175 | directionalLight.diffuse = lightingProperty.diffuseColor ?? Color3.White(); 176 | directionalLight.specular = lightingProperty.specularColor ?? Color3.White(); 177 | } else { 178 | light.dispose(); 179 | this._scene.removeLight(light); 180 | this._lights.delete(light.name); 181 | } 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/manager/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from './observableManager'; 18 | export * from './sceneManager'; 19 | -------------------------------------------------------------------------------- /src/manager/observableManager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import '@babylonjs/core/Misc/observable.extensions'; 18 | import { Camera as BabylonCamera } from '@babylonjs/core/Cameras/camera'; 19 | import { Engine } from '@babylonjs/core/Engines/engine'; 20 | import { Observable } from '@babylonjs/core/Misc/observable'; 21 | import { IDisposable, Scene } from '@babylonjs/core/scene'; 22 | 23 | import { Camera } from '../camera/camera'; 24 | import { CameraConfig } from '../config/cameraConfig'; 25 | import { Config } from '../config/config'; 26 | import { LightingConfig } from '../config/lightingConfig'; 27 | import { SceneConfig } from '../config/sceneConfig'; 28 | import { Lighting } from '../lighting/lighting'; 29 | import { Model } from '../model/model'; 30 | import { AbstractScene } from '../scene/abstractScene'; 31 | 32 | import { SceneManager } from './sceneManager'; 33 | 34 | /** 35 | * This interface describes the structure of the variable sent with the config observables of the scene manager. 36 | * 37 | * O - the type of object we are dealing with (Lighting, Camera, Scene, etc.') 38 | * 39 | * C - the config type 40 | */ 41 | export interface PostConfigurationCallback { 42 | newConfig: C; 43 | sceneManager: SceneManager; 44 | object: O; 45 | } 46 | 47 | /** 48 | * ObservableManager manages all observables 49 | */ 50 | export class ObservableManager implements IDisposable { 51 | /** 52 | * Will notify when Config changed. 53 | */ 54 | private readonly _onConfigChangedObservable: Observable; 55 | 56 | /** 57 | * Will notify when the viewer init started (after config was loaded). 58 | */ 59 | private readonly _onViewerInitStartedObservable: Observable; 60 | 61 | /** 62 | * Observers registered here will be executed when the entire load process has finished. 63 | */ 64 | private readonly _onViewerInitDoneObservable: Observable; 65 | 66 | /** 67 | * Will notify when the scene was created. 68 | */ 69 | private readonly _onSceneCreatedObservable: Observable; 70 | 71 | /** 72 | * Will notify when the engine was created 73 | */ 74 | private readonly _onEngineCreatedObservable: Observable; 75 | 76 | /** 77 | * Will notify when a new model was added to the scene. 78 | * Note that added does not necessarily mean loaded! 79 | */ 80 | private readonly _onModelAddedObservable: Observable; 81 | 82 | /** 83 | * Will notify after every model load. 84 | */ 85 | private readonly _onModelLoadedObservable: Observable; 86 | 87 | /** 88 | * Will notify when any model load failed. 89 | */ 90 | private readonly _onModelLoadErrorObservable: Observable<{ message: string; exception: unknown }>; 91 | 92 | /** 93 | * Will notify when a model was removed from the scene. 94 | */ 95 | private readonly _onModelRemovedObservable: Observable; 96 | 97 | /** 98 | * Functions added to this observable will be executed on each frame rendered. 99 | */ 100 | private readonly _onFrameRenderedObservable: Observable; 101 | 102 | /** 103 | * Will notify at the first time when camera changed its view matrix. 104 | */ 105 | private readonly _onCameraFirstInteractionObservable: Observable; 106 | 107 | /** 108 | * Will notify after the scene was configured. Can be used to further configure the scene. 109 | */ 110 | private readonly _onSceneConfiguredObservable: Observable>; 111 | 112 | /** 113 | * Will notify after the cameras ware configured. Can be used to further configure the camera. 114 | */ 115 | private readonly _onCamerasConfiguredObservable: Observable>; 116 | 117 | /** 118 | * Will notify after the camera was created. 119 | */ 120 | private readonly _onCameraCreatedObservable: Observable; 121 | 122 | /** 123 | * Will notify after the lights were configured. Can be used to further configure lights. 124 | */ 125 | private readonly _onLightingConfiguredObservable: Observable>; 126 | 127 | /** 128 | * Will notify after the error was emitted. 129 | */ 130 | private readonly _onErrorObservable: Observable; 131 | 132 | /** 133 | * Instantiate a new ObservableManager 134 | */ 135 | public constructor() { 136 | this._onConfigChangedObservable = new Observable(); 137 | this._onViewerInitStartedObservable = new Observable(); 138 | this._onViewerInitDoneObservable = new Observable(); 139 | this._onSceneCreatedObservable = new Observable(); 140 | this._onEngineCreatedObservable = new Observable(); 141 | this._onModelLoadedObservable = new Observable(); 142 | this._onModelLoadErrorObservable = new Observable(); 143 | this._onModelAddedObservable = new Observable(); 144 | this._onModelRemovedObservable = new Observable(); 145 | this._onFrameRenderedObservable = new Observable(); 146 | this._onCameraFirstInteractionObservable = new Observable(); 147 | this._onSceneConfiguredObservable = new Observable(); 148 | this._onCamerasConfiguredObservable = new Observable(); 149 | this._onCameraCreatedObservable = new Observable(); 150 | this._onLightingConfiguredObservable = new Observable(); 151 | this._onErrorObservable = new Observable(); 152 | } 153 | 154 | /** 155 | * Will notify when Config changed 156 | */ 157 | public get onConfigChangedObservable(): Observable { 158 | return this._onConfigChangedObservable; 159 | } 160 | 161 | /** 162 | * Will notify when the viewer init started (after config was loaded) 163 | */ 164 | public get onViewerInitStartedObservable(): Observable { 165 | return this._onViewerInitStartedObservable; 166 | } 167 | 168 | /** 169 | * Observers registered here will be executed when the entire load process has finished 170 | */ 171 | public get onViewerInitDoneObservable(): Observable { 172 | return this._onViewerInitDoneObservable; 173 | } 174 | 175 | /** 176 | * Will notify when the scene was created 177 | */ 178 | public get onSceneCreatedObservable(): Observable { 179 | return this._onSceneCreatedObservable; 180 | } 181 | 182 | /** 183 | * Will notify when the engine was created 184 | */ 185 | public get onEngineCreatedObservable(): Observable { 186 | return this._onEngineCreatedObservable; 187 | } 188 | 189 | /** 190 | * Will notify when a new model was added to the scene. 191 | * Note that added does not necessarily mean loaded! 192 | */ 193 | public get onModelAddedObservable(): Observable { 194 | return this._onModelAddedObservable; 195 | } 196 | 197 | /** 198 | * Will notify after every model load 199 | */ 200 | public get onModelLoadedObservable(): Observable { 201 | return this._onModelLoadedObservable; 202 | } 203 | 204 | /** 205 | * Will notify when any model load failed 206 | */ 207 | public get onModelLoadErrorObservable(): Observable<{ message: string; exception: unknown }> { 208 | return this._onModelLoadErrorObservable; 209 | } 210 | 211 | /** 212 | * Will notify when a model was removed from the scene 213 | */ 214 | public get onModelRemovedObservable(): Observable { 215 | return this._onModelRemovedObservable; 216 | } 217 | 218 | /** 219 | * Functions added to this observable will be executed on each frame rendered 220 | */ 221 | public get onFrameRenderedObservable(): Observable { 222 | return this._onFrameRenderedObservable; 223 | } 224 | 225 | /** 226 | * Will notify at the first time when camera changed its view matrix 227 | */ 228 | public get onCameraFirstInteractionObservable(): Observable { 229 | return this._onCameraFirstInteractionObservable; 230 | } 231 | 232 | /** 233 | * Will notify after the scene was configured. Can be used to further configure the scene. 234 | */ 235 | public get onSceneConfiguredObservable(): Observable> { 236 | return this._onSceneConfiguredObservable; 237 | } 238 | 239 | /** 240 | * Will notify after the cameras ware configured. Can be used to further configure the camera. 241 | */ 242 | public get onCamerasConfiguredObservable(): Observable> { 243 | return this._onCamerasConfiguredObservable; 244 | } 245 | 246 | /** 247 | * Will notify after the camera was created. 248 | */ 249 | public get onCameraCreatedObservable(): Observable { 250 | return this._onCameraCreatedObservable; 251 | } 252 | 253 | /** 254 | * Will notify after the lights were configured. Can be used to further configure lights. 255 | */ 256 | public get onLightingConfiguredObservable(): Observable> { 257 | return this._onLightingConfiguredObservable; 258 | } 259 | 260 | /** 261 | * Will notify after the error was emitted. 262 | */ 263 | public get onErrorObservable(): Observable { 264 | return this._onErrorObservable; 265 | } 266 | 267 | /** 268 | * Release the current resources held by ObservableManager 269 | */ 270 | public dispose() { 271 | this.onConfigChangedObservable.clear(); 272 | this.onViewerInitStartedObservable.clear(); 273 | this.onViewerInitDoneObservable.clear(); 274 | this.onSceneCreatedObservable.clear(); 275 | this.onEngineCreatedObservable.clear(); 276 | this.onModelLoadedObservable.clear(); 277 | this.onModelLoadErrorObservable.clear(); 278 | this.onModelAddedObservable.clear(); 279 | this.onModelRemovedObservable.clear(); 280 | this.onFrameRenderedObservable.clear(); 281 | this.onCameraFirstInteractionObservable.clear(); 282 | this.onSceneConfiguredObservable.clear(); 283 | this.onCamerasConfiguredObservable.clear(); 284 | this.onCameraCreatedObservable.clear(); 285 | this.onLightingConfiguredObservable.clear(); 286 | this.onErrorObservable.clear(); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/material/depthMaterial.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Material } from '@babylonjs/core/Materials/material'; 18 | import { MaterialDefines } from '@babylonjs/core/Materials/materialDefines'; 19 | import { MaterialPluginBase } from '@babylonjs/core/Materials/materialPluginBase'; 20 | import { UniformBuffer } from '@babylonjs/core/Materials/uniformBuffer'; 21 | 22 | export interface Uniforms { 23 | ubo: { name: string; size: number; type: string }[]; 24 | fragment: string; 25 | } 26 | 27 | const DEPTH_MAT_NAME = 'Depth'; 28 | const VD_DARKNESS = 'vdDarkness'; 29 | const VD_GAMMA = 'vdGamma'; 30 | 31 | /** 32 | * Material with depth plugin 33 | */ 34 | export class DepthPluginMaterial extends MaterialPluginBase { 35 | private _enabled: boolean; 36 | 37 | private _darkness: number; 38 | 39 | private _gamma: number; 40 | 41 | /** 42 | * Instantiate a new {@link DepthPluginMaterial} 43 | * @param material - base Material to wrap 44 | */ 45 | public constructor(material: Material) { 46 | super(material, DEPTH_MAT_NAME, 200, { DEPTH_MAT: false }); 47 | this._enabled = false; 48 | this._darkness = 1.0; 49 | this._gamma = 1.0; 50 | } 51 | 52 | /** 53 | * Gets whether the plugin is enabled 54 | */ 55 | public get enabled(): boolean { 56 | return this._enabled; 57 | } 58 | 59 | /** 60 | * Gets the darkness value 61 | */ 62 | public get darkness(): number { 63 | return this._darkness; 64 | } 65 | 66 | /** 67 | * Gets the gamma value 68 | */ 69 | public get gamma(): number { 70 | return this._gamma; 71 | } 72 | 73 | /** 74 | * Sets whether the plugin is enabled 75 | */ 76 | public set enabled(enabled: boolean) { 77 | if (this._enabled === enabled) { 78 | return; 79 | } 80 | this._enabled = enabled; 81 | // Mark all shader defines as dirty, triggering a recompile of the shaders 82 | this.markAllDefinesAsDirty(); 83 | // Enable or disable the plugin based on the new state 84 | this._enable(this._enabled); 85 | } 86 | 87 | /** 88 | * Sets the darkness value 89 | */ 90 | public set darkness(targetDarkness: number) { 91 | this._darkness = targetDarkness; 92 | } 93 | 94 | /** 95 | * Sets the gamma value 96 | */ 97 | public set gamma(targetGamma: number) { 98 | this._gamma = targetGamma; 99 | } 100 | 101 | /** 102 | * Sets the defines for the next rendering 103 | * @param defines - the list of "defines" to update. 104 | */ 105 | public override prepareDefines(defines: MaterialDefines): void { 106 | defines.DEPTH_MAT = this._enabled; 107 | } 108 | 109 | /** 110 | * Gets the uniforms for the shader 111 | * @returns the description of the uniforms 112 | */ 113 | public override getUniforms(): Uniforms { 114 | return { 115 | ubo: [ 116 | { name: VD_DARKNESS, size: 1, type: 'float' }, 117 | { name: VD_GAMMA, size: 1, type: 'float' }, 118 | ], 119 | fragment: ` 120 | #ifdef DEPTH_MAT 121 | uniform float ${VD_DARKNESS}; 122 | uniform float ${VD_GAMMA}; 123 | #endif`, 124 | }; 125 | } 126 | 127 | /** 128 | * Binds the material data. 129 | * @param uniformBuffer - defines the Uniform buffer to fill in. 130 | */ 131 | public override bindForSubMesh(uniformBuffer: UniformBuffer): void { 132 | if (this._enabled) { 133 | uniformBuffer.updateFloat(VD_DARKNESS, this.darkness); 134 | uniformBuffer.updateFloat(VD_GAMMA, this.gamma); 135 | } 136 | } 137 | 138 | /** 139 | * Returns a list of custom shader code fragments to customize the shader. 140 | * @param shaderType - "vertex" or "fragment" 141 | * @returns a list of pointName =\> code. 142 | * Note that `pointName` can also be a regular expression if it starts with a `!`. 143 | * In that case, the string found by the regular expression (if any) will be 144 | * replaced by the code provided. 145 | */ 146 | public override getCustomCode(shaderType: 'vertex' | 'fragment'): { [pointName: string]: string } { 147 | return shaderType === 'vertex' 148 | ? { 149 | CUSTOM_VERTEX_DEFINITIONS: ` 150 | varying vec2 vdZW; 151 | `, 152 | CUSTOM_VERTEX_MAIN_END: ` 153 | vdZW = gl_Position.zw; 154 | `, 155 | } 156 | : { 157 | CUSTOM_FRAGMENT_DEFINITIONS: ` 158 | varying vec2 vdZW; 159 | `, 160 | CUSTOM_FRAGMENT_MAIN_BEGIN: ` 161 | #ifdef DEPTH_MAT 162 | float vdDepth = 0.5 * vdZW.x / vdZW.y + 0.5; 163 | vdDepth = pow(vdDepth, ${VD_GAMMA}); 164 | gl_FragColor = vec4(vec3(0.), clamp((1.0 - vdDepth) * vdDarkness, 0., 1.)); 165 | return; 166 | #endif 167 | `, 168 | }; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/material/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from './depthMaterial'; 18 | -------------------------------------------------------------------------------- /src/model/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from './model'; 18 | export * from './modelLoader'; 19 | -------------------------------------------------------------------------------- /src/model/modelLoader.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import '@babylonjs/core/Materials/Textures/Loaders/basisTextureLoader'; 18 | import '@babylonjs/core/Materials/Textures/Loaders/ktxTextureLoader'; 19 | import '@babylonjs/loaders/glTF'; 20 | import { AssetContainer } from '@babylonjs/core/assetContainer'; 21 | import { LoadAssetContainerAsync, LoadAssetContainerOptions } from '@babylonjs/core/Loading/sceneLoader'; 22 | import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh'; 23 | import { IDisposable } from '@babylonjs/core/scene'; 24 | import { GLTFLoaderAnimationStartMode } from '@babylonjs/loaders/glTF/glTFFileLoader'; 25 | 26 | import { ObservableManager } from '../manager/observableManager'; 27 | import { SceneManager } from '../manager/sceneManager'; 28 | 29 | import { Model } from './model'; 30 | 31 | export interface LoadGltfOptions { 32 | /** 33 | * When loading glTF animations, which are defined in seconds, target them to this FPS. Defaults to 60. 34 | */ 35 | targetFps?: number; 36 | } 37 | 38 | /** 39 | * Responsible for loading models 40 | */ 41 | export class ModelLoader implements IDisposable { 42 | private readonly _sceneManager: SceneManager; 43 | private readonly _observableManager: ObservableManager; 44 | 45 | /** 46 | * Instantiate a new {@link ModelLoader} 47 | * @param sceneManager - an instance of {@link SceneManager} 48 | * @param observableManager - an instance of {@link ObservableManager} 49 | */ 50 | public constructor(sceneManager: SceneManager, observableManager: ObservableManager) { 51 | this._sceneManager = sceneManager; 52 | this._observableManager = observableManager; 53 | } 54 | 55 | /** 56 | * Import a glTF or glb file into the scene. 57 | * Either a URL path to the file must be provided, or the base64 based string of a glb file (starts with 'data:'). 58 | * 59 | * @param url - URL path to the glTF or glb file, or base64 based string (starts with 'data:') 60 | * @param disableAnimation - whether disable animation of the loaded model (default: false) 61 | * @param modelName - if provided, set the model name 62 | * @param options - optional config, {@link LoadGltfOptions} 63 | * @returns A promise of {@link Model} 64 | * 65 | * @throws Error if no glTF content or url provided 66 | */ 67 | public async loadGltf( 68 | url: string, 69 | disableAnimation?: boolean, 70 | modelName?: string, 71 | options?: LoadGltfOptions, 72 | ): Promise { 73 | if (!url) { 74 | throw Error('No glTF content or url provided'); 75 | } 76 | 77 | let pluginExtension = '.glb'; 78 | let isBase64 = false; 79 | let isBlobUrl = false; 80 | const extensionRegEx = /\.(glb|gltf|babylon|obj|stl)$/; 81 | 82 | if (url.startsWith('data:')) { 83 | isBase64 = true; 84 | } else if (url.startsWith('blob:')) { 85 | isBlobUrl = true; 86 | } else if (extensionRegEx.test(url.toLowerCase())) { 87 | pluginExtension = url.toLowerCase().match(extensionRegEx)?.at(0) ?? pluginExtension; 88 | } 89 | 90 | const loadAssetContainerOptions: LoadAssetContainerOptions = { 91 | pluginOptions: { 92 | gltf: { 93 | // Use HTTP range requests to load the glTF binary (GLB) in parts. 94 | useRangeRequests: !isBase64 && !isBlobUrl, 95 | extensionOptions: { 96 | /* For debugging MSFT_lod extension */ 97 | // MSFT_lod: { 98 | // maxLODsToLoad: 1, 99 | // }, 100 | }, 101 | }, 102 | }, 103 | pluginExtension: pluginExtension, 104 | }; 105 | 106 | if (disableAnimation) { 107 | loadAssetContainerOptions.pluginOptions!.gltf!.animationStartMode = GLTFLoaderAnimationStartMode.NONE; 108 | } 109 | 110 | if (options?.targetFps) { 111 | loadAssetContainerOptions.pluginOptions!.gltf!.targetFps = options.targetFps; 112 | } 113 | 114 | /* For debugging MSFT_lod extension */ 115 | // SceneLoader.OnPluginActivatedObservable.addOnce(function (loader) { 116 | // if (loader.name === 'gltf' && loader instanceof GLTFFileLoader) { 117 | // loader.loggingEnabled = true; 118 | // } 119 | // }); 120 | 121 | if (this._sceneManager.config.sceneConfig?.useLoadingUI) { 122 | this._sceneManager.scene.getEngine().displayLoadingUI(); 123 | } 124 | 125 | let container: AssetContainer; 126 | try { 127 | container = await LoadAssetContainerAsync(url, this._sceneManager.scene, loadAssetContainerOptions); 128 | } catch (error) { 129 | // Fallback to full download on range request failure 130 | if (error instanceof Error && error.message.includes('RangeError')) { 131 | loadAssetContainerOptions.pluginOptions!.gltf!.useRangeRequests = false; 132 | container = await LoadAssetContainerAsync(url, this._sceneManager.scene, loadAssetContainerOptions); 133 | } else { 134 | throw error; 135 | } 136 | } 137 | 138 | // Add everything from the container into the scene 139 | container.addAllToScene(); 140 | 141 | if (this._sceneManager.config.sceneConfig?.useLoadingUI) { 142 | this._sceneManager.scene.getEngine().hideLoadingUI(); 143 | } 144 | 145 | // Avoid frustum clipping for all meshes 146 | container.meshes.forEach((mesh: AbstractMesh) => { 147 | mesh.alwaysSelectAsActiveMesh = true; 148 | }); 149 | 150 | const model = new Model(this._sceneManager, this._observableManager, container, modelName); 151 | this._sceneManager.models.set(model.name, model); 152 | 153 | this._observableManager.onModelLoadedObservable.notifyObservers(model); 154 | 155 | return Promise.resolve(model); 156 | } 157 | 158 | /** 159 | * Release all resources held by {@link ModelLoader} 160 | */ 161 | public dispose(): void {} 162 | } 163 | -------------------------------------------------------------------------------- /src/scene/defaultScene.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Nullable } from '@babylonjs/core/types'; 18 | 19 | import { Config } from '../config/config'; 20 | import { DEFAULT_CONFIG } from '../config/preset/defaultConfig'; 21 | 22 | import { AbstractScene } from './abstractScene'; 23 | 24 | /** 25 | * DefaultScene for general use-cases 26 | */ 27 | class DefaultScene extends AbstractScene { 28 | /** 29 | * Instantiate a new {@link DefaultScene} 30 | * @param canvas - defines the canvas to use for rendering. If NullEngine is used, set the canvas as null. 31 | * @param presetConfig - pre-defined configuration. By default, use {@link DEFAULT_CONFIG}. 32 | * @param configOverride - used to override parameters in pre-defined configuration 33 | */ 34 | public constructor( 35 | canvas: Nullable, 36 | presetConfig: Config = DEFAULT_CONFIG, 37 | configOverride: Config = {}, 38 | ) { 39 | super(canvas, presetConfig, configOverride); 40 | } 41 | } 42 | 43 | export { DefaultScene }; 44 | -------------------------------------------------------------------------------- /src/scene/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export * from './abstractScene'; 18 | export * from './defaultScene'; 19 | export * from './v3dScene'; 20 | -------------------------------------------------------------------------------- /src/scene/v3dScene.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Engine } from '@babylonjs/core/Engines/engine'; 18 | import { Nullable } from '@babylonjs/core/types'; 19 | 20 | import { Config } from '../config/config'; 21 | import { V3D_CONFIG } from '../config/preset/v3dConfig'; 22 | 23 | import { AbstractScene } from './abstractScene'; 24 | 25 | /** 26 | * V3DScene is used by View-in-3D viewer 27 | */ 28 | export class V3DScene extends AbstractScene { 29 | /** 30 | * Instantiate a new {@link V3DScene} 31 | * @param canvas - defines the canvas to use for rendering. If NullEngine is used, set the canvas as null. 32 | * @param presetConfig - pre-defined configuration. By default, use {@link V3D_CONFIG}. 33 | * @param configOverride - used to override parameters in pre-defined configuration 34 | */ 35 | public constructor( 36 | canvas: Nullable, 37 | presetConfig: Config = V3D_CONFIG, 38 | configOverride: Config = {}, 39 | ) { 40 | super(canvas, presetConfig, configOverride); 41 | 42 | // Override the hardware scaling level 43 | this.observableManager.onEngineCreatedObservable.add((engine: Engine) => { 44 | engine.setHardwareScalingLevel((1 / window.devicePixelRatio) * 0.75); 45 | return Promise.resolve(engine); 46 | }); 47 | } 48 | 49 | /** 50 | * Register a callback that will be invoked after camera first interaction 51 | * @param callback - will be invoked after camera first interaction 52 | */ 53 | public setOnCameraFirstInteraction(callback: () => void) { 54 | this.observableManager.onCameraFirstInteractionObservable.addOnce(() => { 55 | callback(); 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/util/convex-hull.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Declaration file for [convex-hull](https://github.com/mikolalysenko/convex-hull) 3 | */ 4 | declare module 'convex-hull' { 5 | /** 6 | * Computes the convex hull of points 7 | * @param points - an array of points encoded as `d` length arrays 8 | * @returns a polytope encoding the convex hull of the point set 9 | */ 10 | export default function convexHull(points: Array>): Array>; 11 | } 12 | -------------------------------------------------------------------------------- /src/util/convexHull.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Vector3 } from '@babylonjs/core/Maths/math.vector'; 18 | import { FloatArray } from '@babylonjs/core/types'; 19 | import getConvexHull from 'convex-hull'; 20 | 21 | /** 22 | * Class for computing the convex hull of vertices 23 | */ 24 | export class ConvexHull { 25 | /** 26 | * Compute the faces of the convex hull 27 | * @param allVertices - position of all vertices 28 | * @returns a list of faces. A face consists of a list of vertex indices. 29 | */ 30 | public static getFaces(allVertices: Array): Array> { 31 | return getConvexHull( 32 | allVertices.map((vertex) => { 33 | const array: FloatArray = []; 34 | vertex.toArray(array); 35 | return array; 36 | }), 37 | ); 38 | } 39 | 40 | /** 41 | * Compute the indices of the vertices of the convex hull 42 | * @param allVertices - position of all vertices 43 | * @returns a list of vertex indices 44 | */ 45 | public static getVertexIndices(allVertices: Array): Array { 46 | const faces = ConvexHull.getFaces(allVertices); 47 | const indices = faces.reduce((acc, val) => { 48 | acc.push(...val); 49 | return acc; 50 | }, []); 51 | const uniqueIndexSet = new Set(indices); 52 | return Array.from(uniqueIndexSet.values()); 53 | } 54 | 55 | /** 56 | * Compute the vertices of the convex hull 57 | * @param allVertices - position of all vertices 58 | * @returns a list of vertices 59 | */ 60 | public static getVertices(allVertices: Array): Array { 61 | return ConvexHull.getVertexIndices(allVertices).map((index) => allVertices[index]); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/util/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera'; 18 | import { PBRMaterial } from '@babylonjs/core/Materials/PBR/pbrMaterial'; 19 | import { Matrix, Vector3 } from '@babylonjs/core/Maths/math.vector'; 20 | import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh'; 21 | import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder'; 22 | import { StringTools } from '@babylonjs/core/Misc/stringTools'; 23 | import { Nullable } from '@babylonjs/core/types'; 24 | 25 | import { AbstractScene } from '../scene/abstractScene'; 26 | 27 | export * from './convexHull'; 28 | 29 | /** 30 | * Class used to store all common util functions 31 | */ 32 | export class Util { 33 | /** 34 | * Check if the file is a texture asset 35 | * @param name - file name 36 | * @returns `true` if the file is a texture asset, else `false` 37 | */ 38 | public static isTextureAsset(name: string): boolean { 39 | const extensions = ['.ktx', '.ktx2', '.png', '.jpg', '.jpeg', '.basis']; 40 | 41 | const queryStringIndex = name.indexOf('?'); 42 | if (queryStringIndex !== -1) { 43 | name = name.substring(0, queryStringIndex); 44 | } 45 | 46 | return extensions.some((extension: string) => StringTools.EndsWith(name, extension)); 47 | } 48 | 49 | /** 50 | * Create a metallic sphere to the scene 51 | * @param scene - an instance of class which extends {@link AbstractScene} 52 | * @param sphereProportionOfScreen - controls the size of sphere 53 | * @param borderOffsetPx - canvas boarder offset in pixel 54 | * @param placement - controls where to place the sphere, default: 'top-right' 55 | */ 56 | public static createMetallicSphere( 57 | scene: AbstractScene, 58 | sphereProportionOfScreen: number = 0.15, 59 | borderOffsetPx: number = 25, 60 | placement: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' = 'top-right', 61 | ): Nullable { 62 | const sceneManager = scene.sceneManager; 63 | if (!sceneManager?.scene) { 64 | return null; 65 | } 66 | 67 | const meshName = Constant.LIGHTING_REFLECTION_SPHERE; 68 | const existingMesh = sceneManager.scene.getMeshByName(meshName); 69 | if (existingMesh) { 70 | return existingMesh; 71 | } 72 | 73 | const pbr = new PBRMaterial(Constant.METALLIC_SPHERE_MATERIAL, sceneManager.scene); 74 | pbr.metallic = 1.0; 75 | pbr.roughness = 0; 76 | pbr.subSurface.isRefractionEnabled = false; 77 | 78 | const metallicSphere = MeshBuilder.CreateSphere(meshName, { segments: 32, diameter: 1 }, sceneManager.scene); 79 | metallicSphere.material = pbr; 80 | metallicSphere.renderingGroupId = 1; 81 | metallicSphere.isPickable = false; 82 | 83 | const camera = sceneManager.scene.activeCamera; 84 | if (!camera || !(camera instanceof ArcRotateCamera)) { 85 | return metallicSphere; 86 | } 87 | 88 | // Update metallic sphere position and scale 89 | sceneManager.scene.onBeforeRenderObservable.add(() => { 90 | const arcRotateCamera = camera as ArcRotateCamera; 91 | 92 | // Make sure lighting sphere is positioned properly given window size 93 | arcRotateCamera.getViewMatrix(); 94 | 95 | const placementSignX = placement.indexOf('left') == -1 ? 1 : -1; 96 | const placementSignY = placement.indexOf('bottom') == -1 ? 1 : -1; 97 | const invertedCameraViewProjectionMatrix = Matrix.Invert(arcRotateCamera.getTransformationMatrix()); 98 | const screenWidth = sceneManager.scene.getEngine().getRenderWidth(true); 99 | const screenHeight = sceneManager.scene.getEngine().getRenderHeight(true); 100 | const nearPlaneHeight = 4 * arcRotateCamera.minZ * Math.tan(arcRotateCamera.fov / 2); 101 | const scalingDeterminant = nearPlaneHeight * sphereProportionOfScreen; 102 | const aspectRatio = screenWidth / screenHeight; 103 | const point = new Vector3(placementSignX * 1, placementSignY * 1, 0); 104 | metallicSphere.scalingDeterminant = scalingDeterminant; 105 | point.x += -1 * placementSignX * (sphereProportionOfScreen / aspectRatio + borderOffsetPx / screenWidth); 106 | // No y-adjustment necessary due to the fovMode of the camera being FOV_MODE_VERTICAL_FIXED by default 107 | point.y += -1 * placementSignY * (sphereProportionOfScreen + borderOffsetPx / screenHeight); 108 | // Positions mesh according to the camera's matrix transformation 109 | metallicSphere.position = Vector3.TransformCoordinates(point, invertedCameraViewProjectionMatrix); 110 | }); 111 | 112 | return metallicSphere; 113 | } 114 | } 115 | 116 | export class Constant { 117 | public static BACKGROUND_CAMERA: string = 'BackgroundCamera'; 118 | public static DEPTH_MAP_RENDER_TARGET_TEXTURE: string = 'DepthMapRenderTargetTexture'; 119 | public static DEPTH_MAP_RTT_MATERIAL: string = 'DepthMapRTTMaterial'; 120 | public static DEPTH_MAP_SHADOW_CAMERA: string = 'DepthMapShadowCamera'; 121 | public static DEPTH_MAP_SHADOW_MATERIAL: string = 'DepthMapShadowMaterial'; 122 | public static DEPTH_MAP_SHADOW_PLANE: string = 'DepthMapShadowPlane'; 123 | public static DIRECTIONAL_SHADOW_LIGHT: string = 'DirectionalShadowLight'; 124 | public static LIGHTING_REFLECTION_SPHERE: string = 'LightingReflectionSphere'; 125 | public static MATERIAL_CAPTURE_MATERIAL: string = 'MatCapMaterial'; 126 | public static METALLIC_SPHERE_MATERIAL: string = 'MetallicSphereMaterial'; 127 | public static SHADOW_ONLY_MATERIAL: string = 'ShadowOnlyMaterial'; 128 | public static SHADOW_PLANE: string = 'ShadowPlane'; 129 | } 130 | -------------------------------------------------------------------------------- /test/camera/camera.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { CubicEase } from '@babylonjs/core/Animations/easing'; 18 | import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera'; 19 | import { NullEngine } from '@babylonjs/core/Engines/nullEngine'; 20 | import { Vector3 } from '@babylonjs/core/Maths/math.vector'; 21 | import { Logger } from '@babylonjs/core/Misc/logger'; 22 | import { Scene } from '@babylonjs/core/scene'; 23 | import { Nullable } from '@babylonjs/core/types'; 24 | 25 | import { Camera } from '../../src/camera/camera'; 26 | import { CameraConfig } from '../../src/config/cameraConfig'; 27 | import { ObservableManager } from '../../src/manager/observableManager'; 28 | import { SceneManager } from '../../src/manager/sceneManager'; 29 | 30 | Logger.LogLevels = Logger.ErrorLogLevel; 31 | 32 | describe('Camera', () => { 33 | let engine: Nullable = null; 34 | let scene: Nullable = null; 35 | let sceneManager: Nullable = null; 36 | let observableManager: Nullable = null; 37 | let camera: Nullable = null; 38 | 39 | beforeEach(async () => { 40 | engine = new NullEngine(); 41 | scene = new Scene(engine); 42 | observableManager = new ObservableManager(); 43 | sceneManager = new SceneManager(engine, {}, observableManager); 44 | await sceneManager.createScene(scene); 45 | sceneManager.runRenderLoop(); 46 | camera = new Camera(sceneManager, observableManager); 47 | }); 48 | 49 | afterEach(() => { 50 | camera?.dispose(); 51 | sceneManager?.dispose(); 52 | observableManager?.dispose(); 53 | engine?.dispose(); 54 | }); 55 | 56 | it('should do nothing if no camera settings in CameraConfig', async () => { 57 | const defaultCameraName = scene?.activeCamera?.name; 58 | const cameraConfig: CameraConfig = {}; 59 | 60 | await camera?.updateCameras(cameraConfig); 61 | 62 | expect(scene?.activeCamera?.name).toBe(defaultCameraName); 63 | }); 64 | 65 | it('should be able to create and remove a camera', async () => { 66 | // Create a camera 67 | const cameraConfig: CameraConfig = { 68 | NewArcRotateCamera: { 69 | type: 'arcRotateCamera', 70 | target: Vector3.One(), 71 | alphaInDegrees: 60, 72 | minimizeRotation: true, 73 | animationDuration: 0.5, 74 | cameraAnimationConfig: { 75 | easingFunction: new CubicEase(), 76 | easingMode: 'EASINGMODE_EASEINOUT', 77 | loopMode: 'ANIMATIONLOOPMODE_CONSTANT', 78 | maxFPS: 60, 79 | }, 80 | autoRotationBehavior: { 81 | enabled: true, 82 | }, 83 | framingBehavior: { 84 | enabled: true, 85 | }, 86 | }, 87 | }; 88 | await camera?.updateCameras(cameraConfig); 89 | expect(scene?.activeCamera?.name).toEqual('NewArcRotateCamera'); 90 | expect((scene?.activeCamera as ArcRotateCamera).useAutoRotationBehavior).toBeTruthy(); 91 | expect((scene?.activeCamera as ArcRotateCamera).useFramingBehavior).toBeTruthy(); 92 | 93 | // Remove a camera 94 | cameraConfig.NewArcRotateCamera.enable = false; 95 | await camera?.updateCameras(cameraConfig); 96 | expect(scene?.getCameraByName('NewArcRotateCamera')).toBeFalsy(); 97 | }); 98 | 99 | test('willAddOrRemoveCameras', async () => { 100 | const cameraConfig: CameraConfig = { 101 | NewArcRotateCamera: { 102 | type: 'arcRotateCamera', 103 | enable: true, 104 | }, 105 | }; 106 | 107 | // Will add camera 108 | let willAddOrRemoveCameras = camera?.willAddOrRemoveCameras(cameraConfig); 109 | expect(willAddOrRemoveCameras).toBe(true); 110 | 111 | // Camera already added 112 | await camera?.updateCameras(cameraConfig); 113 | willAddOrRemoveCameras = camera?.willAddOrRemoveCameras(cameraConfig); 114 | expect(willAddOrRemoveCameras).toBe(false); 115 | 116 | // Will remove camera 117 | cameraConfig.NewArcRotateCamera.enable = false; 118 | willAddOrRemoveCameras = camera?.willAddOrRemoveCameras(cameraConfig); 119 | expect(willAddOrRemoveCameras).toBe(true); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /test/lighting/lighting.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { NullEngine } from '@babylonjs/core/Engines/nullEngine'; 18 | import { Matrix } from '@babylonjs/core/Maths/math.vector'; 19 | import { Logger } from '@babylonjs/core/Misc/logger'; 20 | import { Scene } from '@babylonjs/core/scene'; 21 | import { Nullable } from '@babylonjs/core/types'; 22 | 23 | import { LightingConfig } from '../../src/config/lightingConfig'; 24 | import { Lighting } from '../../src/lighting/lighting'; 25 | import { ObservableManager } from '../../src/manager/observableManager'; 26 | import { SceneManager } from '../../src/manager/sceneManager'; 27 | 28 | Logger.LogLevels = Logger.ErrorLogLevel; 29 | 30 | describe('Lighting', () => { 31 | let engine: Nullable = null; 32 | let scene: Nullable = null; 33 | let sceneManager: Nullable = null; 34 | let observableManager: Nullable = null; 35 | let lighting: Nullable = null; 36 | 37 | beforeEach(async () => { 38 | engine = new NullEngine(); 39 | scene = new Scene(engine); 40 | observableManager = new ObservableManager(); 41 | sceneManager = new SceneManager(engine, {}, observableManager); 42 | await sceneManager.createScene(scene); 43 | lighting = new Lighting(sceneManager); 44 | }); 45 | 46 | afterEach(() => { 47 | lighting?.dispose(); 48 | sceneManager?.dispose(); 49 | observableManager?.dispose(); 50 | engine?.dispose(); 51 | }); 52 | 53 | it('should be able to create and remove environment lighting', async () => { 54 | // Create environment lighting 55 | const lightingConfig: LightingConfig = { 56 | NeutralIBL: { 57 | type: 'env', 58 | filePath: 'public/ibl/Neutral.env', 59 | }, 60 | }; 61 | await lighting?.updateLighting(lightingConfig); 62 | expect(scene?.environmentTexture?.name).toEqual('NeutralIBL'); 63 | 64 | // Remove environment lighting 65 | lightingConfig.NeutralIBL.enable = false; 66 | await lighting?.updateLighting(lightingConfig); 67 | expect(scene?.environmentTexture).toBeFalsy(); 68 | }); 69 | 70 | it('should be able to switch between different environment lighting', async () => { 71 | // Enable NeutralIBL 72 | const lightingConfig: LightingConfig = { 73 | NeutralIBL: { 74 | type: 'env', 75 | enable: true, 76 | filePath: 'public/ibl/Neutral.env', 77 | }, 78 | DirectionalIBL: { 79 | type: 'env', 80 | enable: false, 81 | filePath: 'public/ibl/Directional_colorVariationOnTopBlur100_512.env', 82 | }, 83 | }; 84 | await lighting?.updateLighting(lightingConfig); 85 | expect(scene?.environmentTexture?.name).toEqual('NeutralIBL'); 86 | 87 | // Disable NeutralIBL and enable DirectionalIBL 88 | lightingConfig.NeutralIBL.enable = false; 89 | lightingConfig.DirectionalIBL.enable = true; 90 | await lighting?.updateLighting(lightingConfig); 91 | expect(scene?.environmentTexture?.name).toEqual('DirectionalIBL'); 92 | }); 93 | 94 | it('should be able to create and remove ambient light', async () => { 95 | // Create ambient light 96 | const lightingConfig: LightingConfig = { 97 | AmbientLight: { 98 | type: 'ambient', 99 | }, 100 | }; 101 | await lighting?.updateLighting(lightingConfig); 102 | expect(scene?.getLightByName('AmbientLight')?.name).toEqual('AmbientLight'); 103 | 104 | // Remove ambient light 105 | lightingConfig.AmbientLight.enable = false; 106 | await lighting?.updateLighting(lightingConfig); 107 | expect(scene?.getLightByName('AmbientLight')).toBeFalsy(); 108 | }); 109 | 110 | it('should be able to create and remove directional light', async () => { 111 | // Create directional light 112 | const lightingConfig: LightingConfig = { 113 | DirectionalLight: { 114 | type: 'directional', 115 | }, 116 | }; 117 | await lighting?.updateLighting(lightingConfig); 118 | expect(scene?.getLightByName('DirectionalLight')?.name).toEqual('DirectionalLight'); 119 | 120 | // Remove directional light 121 | lightingConfig.DirectionalLight.enable = false; 122 | await lighting?.updateLighting(lightingConfig); 123 | expect(scene?.getLightByName('DirectionalLight')).toBeFalsy(); 124 | }); 125 | 126 | it('should be able to rotate light', async () => { 127 | const lightingConfig: LightingConfig = { 128 | NeutralIBL: { 129 | type: 'env', 130 | enable: true, 131 | filePath: 'public/ibl/Neutral.env', 132 | textureMatrix: Matrix.RotationY(90), 133 | }, 134 | }; 135 | 136 | await lighting?.updateLighting(lightingConfig); 137 | 138 | expect(scene?.environmentTexture?.name).toEqual('NeutralIBL'); 139 | expect(scene?.environmentTexture?.getReflectionTextureMatrix().asArray()).toEqual( 140 | Matrix.RotationY(90).asArray(), 141 | ); 142 | }); 143 | 144 | it('should throw error if EnvironmentProperty.filePath is empty or undefined', async () => { 145 | const lightingConfig: LightingConfig = { 146 | NeutralIBL: { 147 | type: 'env', 148 | filePath: '', 149 | }, 150 | }; 151 | 152 | await expect(lighting?.updateLighting(lightingConfig)).rejects.toThrow(Error); 153 | }); 154 | }); 155 | -------------------------------------------------------------------------------- /test/manager/sceneManager.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'jest-canvas-mock'; 18 | import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera'; 19 | import { NullEngine } from '@babylonjs/core/Engines/nullEngine'; 20 | import { Color3, Color4 } from '@babylonjs/core/Maths/math.color'; 21 | import { Vector3 } from '@babylonjs/core/Maths/math.vector'; 22 | import { Logger } from '@babylonjs/core/Misc/logger'; 23 | import { Scene } from '@babylonjs/core/scene'; 24 | import { Nullable } from '@babylonjs/core/types'; 25 | 26 | import { Config } from '../../src/config/config'; 27 | import { Constant } from '../../src/index'; 28 | import { ObservableManager } from '../../src/manager/observableManager'; 29 | import { SceneManager } from '../../src/manager/sceneManager'; 30 | 31 | Logger.LogLevels = Logger.ErrorLogLevel; 32 | 33 | describe('SceneManager', () => { 34 | let engine: Nullable = null; 35 | let scene: Nullable = null; 36 | let sceneManager: Nullable = null; 37 | let observableManager: Nullable = null; 38 | let camera: Nullable = null; 39 | 40 | beforeEach(() => { 41 | engine = new NullEngine(); 42 | scene = new Scene(engine); 43 | observableManager = new ObservableManager(); 44 | sceneManager = new SceneManager(engine, {}, observableManager); 45 | camera = new ArcRotateCamera('camera', 0, 0, 10, Vector3.Zero(), scene); 46 | }); 47 | 48 | afterEach(() => { 49 | camera?.dispose(); 50 | sceneManager?.dispose(); 51 | observableManager?.dispose(); 52 | engine?.dispose(); 53 | }); 54 | 55 | describe('createScene', () => { 56 | it('should use provided scene instead of creating a new scene', async () => { 57 | sceneManager = new SceneManager(engine!, {}, observableManager!); 58 | scene = new Scene(engine!); 59 | 60 | await sceneManager.createScene(); 61 | await sceneManager.createScene(scene); 62 | 63 | expect(sceneManager.scene).toEqual(scene); 64 | }); 65 | 66 | it('should be able to create a new scene', async () => { 67 | const config: Config = { 68 | engineConfig: { 69 | engineOptions: { 70 | disableWebGL2Support: true, 71 | }, 72 | }, 73 | }; 74 | sceneManager = new SceneManager(engine!, config, observableManager!); 75 | 76 | await sceneManager.createScene(); 77 | await new Promise((resolve) => { 78 | setTimeout(resolve, 500); 79 | }); 80 | 81 | expect(sceneManager.scene).toBeTruthy(); 82 | }); 83 | }); 84 | 85 | describe('updateConfig', () => { 86 | it('should be able to configure scene', async () => { 87 | sceneManager = new SceneManager(engine!, {}, observableManager!); 88 | await sceneManager.createScene(); 89 | const config: Config = { 90 | sceneConfig: { 91 | clearColor: Color4.FromColor3(Color3.Black()), 92 | }, 93 | }; 94 | 95 | await sceneManager.updateConfig(config); 96 | 97 | expect(sceneManager.scene.clearColor).toEqual(Color4.FromColor3(Color3.Black())); 98 | }); 99 | 100 | it('should be able to configure lighting', async () => { 101 | sceneManager = new SceneManager(engine!, {}, observableManager!); 102 | await sceneManager.createScene(); 103 | const config: Config = { 104 | engineConfig: { 105 | antialiasing: true, 106 | engineOptions: { 107 | disableWebGL2Support: false, 108 | }, 109 | }, 110 | lightingConfig: { 111 | NewDirectionalLight: { 112 | type: 'directional', 113 | }, 114 | }, 115 | }; 116 | 117 | await sceneManager.updateConfig(config); 118 | 119 | expect(sceneManager.scene.getLightByName('NewDirectionalLight')).toBeTruthy(); 120 | }); 121 | 122 | it('should be able to configure camera', async () => { 123 | sceneManager = new SceneManager(engine!, {}, observableManager!); 124 | await sceneManager.createScene(); 125 | const config: Config = { 126 | cameraConfig: { 127 | NewArcRotateCamera: { 128 | type: 'arcRotateCamera', 129 | }, 130 | }, 131 | }; 132 | 133 | await sceneManager.updateConfig(config); 134 | 135 | expect(sceneManager.scene.getCameraByName('NewArcRotateCamera')).toBeTruthy(); 136 | }); 137 | 138 | it('should be able to configure rendering pipeline', async () => { 139 | sceneManager = new SceneManager(engine!, {}, observableManager!); 140 | await sceneManager.createScene(); 141 | const config: Config = { 142 | renderingPipelineConfig: { 143 | defaultRenderingPipeline: { 144 | enable: true, 145 | imageProcessing: { 146 | enable: true, 147 | exposure: 0.8, 148 | }, 149 | }, 150 | }, 151 | }; 152 | 153 | await sceneManager.updateConfig(config); 154 | 155 | expect(sceneManager.scene.imageProcessingConfiguration.exposure).toBe(0.8); 156 | }); 157 | }); 158 | 159 | test('loadTextureAsset', async () => { 160 | sceneManager = new SceneManager(engine!, {}, observableManager!); 161 | 162 | const scene = sceneManager.loadTextureAsset('data:image/gif;base64,R0lGODlhAQABAAAAACw='); 163 | await new Promise((resolve) => { 164 | setTimeout(resolve, 500); 165 | }); 166 | 167 | expect(scene.getTextureByName('data:image/gif;base64,R0lGODlhAQABAAAAACw=')).toBeTruthy(); 168 | expect(scene.getMeshByName('Plane')).toBeTruthy(); 169 | expect(scene.getMaterialByName('UnlitPBRMaterial')).toBeTruthy(); 170 | }); 171 | 172 | test('runRenderLoop', async () => { 173 | sceneManager = new SceneManager(engine!, {}, observableManager!); 174 | await sceneManager.createScene(); 175 | expect(engine!.activeRenderLoops.length).toEqual(0); 176 | 177 | sceneManager.runRenderLoop(); 178 | await sceneManager.scene.whenReadyAsync(); 179 | expect(engine!.activeRenderLoops.length).toEqual(1); 180 | 181 | sceneManager.runRenderLoop(); 182 | await sceneManager.scene.whenReadyAsync(); 183 | expect(engine!.activeRenderLoops.length).toEqual(1); 184 | }); 185 | 186 | test('stopRenderLoop', async () => { 187 | sceneManager = new SceneManager(engine!, {}, observableManager!); 188 | await sceneManager.createScene(); 189 | expect(engine!.activeRenderLoops.length).toEqual(0); 190 | 191 | sceneManager.runRenderLoop(); 192 | await sceneManager.scene.whenReadyAsync(); 193 | expect(engine!.activeRenderLoops.length).toEqual(1); 194 | 195 | sceneManager.stopRenderLoop(); 196 | expect(engine!.activeRenderLoops.length).toEqual(0); 197 | }); 198 | 199 | test('should be able to configure SSAO rendering pipeline', async () => { 200 | // Suppress the error message 'PrePassRenderer needs WebGL 2 support' 201 | Logger.LogLevels = Logger.NoneLogLevel; 202 | 203 | sceneManager = new SceneManager(engine!, {}, observableManager!); 204 | await sceneManager.createScene(); 205 | const config: Config = { 206 | engineConfig: { 207 | antialiasing: true, 208 | engineOptions: { 209 | disableWebGL2Support: false, 210 | }, 211 | }, 212 | renderingPipelineConfig: { 213 | ssao2RenderingPipeline: { 214 | enable: true, 215 | }, 216 | }, 217 | }; 218 | 219 | await sceneManager.updateConfig(config); 220 | 221 | expect(sceneManager.config.renderingPipelineConfig!.ssao2RenderingPipeline!.enable).toBe(true); 222 | 223 | Logger.LogLevels = Logger.ErrorLogLevel; 224 | }); 225 | 226 | test('Check active cameras if scene as one active camera', async () => { 227 | sceneManager = new SceneManager(engine!, {}, observableManager!); 228 | await sceneManager.createScene(); 229 | const config: Config = { 230 | renderingPipelineConfig: { 231 | defaultRenderingPipeline: { 232 | enable: true, 233 | samples: 4, 234 | imageProcessing: { 235 | enable: true, 236 | contrast: 2, 237 | }, 238 | }, 239 | }, 240 | }; 241 | sceneManager.scene.activeCamera = camera; 242 | await sceneManager.updateConfig(config); 243 | if (sceneManager.scene.activeCameras?.length) { 244 | expect(sceneManager.scene.activeCameras[0]?.name).toBe(Constant.BACKGROUND_CAMERA); 245 | expect(sceneManager.scene.activeCameras[1]?.name).toBe('camera'); 246 | } 247 | }); 248 | 249 | test('Check active cameras if scene as multiple active cameras', async () => { 250 | const camera1 = new ArcRotateCamera('camera1', 0, 0, 10, Vector3.Zero(), scene!); 251 | const camera2 = new ArcRotateCamera('camera2', 0, 0, 10, Vector3.Zero(), scene!); 252 | sceneManager = new SceneManager(engine!, {}, observableManager!); 253 | await sceneManager.createScene(); 254 | const config: Config = { 255 | renderingPipelineConfig: { 256 | defaultRenderingPipeline: { 257 | enable: true, 258 | samples: 4, 259 | imageProcessing: { 260 | enable: true, 261 | contrast: 2, 262 | }, 263 | }, 264 | }, 265 | }; 266 | sceneManager.scene.activeCameras = [camera1, camera2]; 267 | await sceneManager.updateConfig(config); 268 | 269 | if (sceneManager.scene.activeCameras?.length) { 270 | expect(sceneManager.scene.activeCameras[0].name).toBe(Constant.BACKGROUND_CAMERA); 271 | expect(sceneManager.scene.activeCameras[1].name).toBe('camera1'); 272 | expect(sceneManager.scene.activeCameras[2].name).toBe('camera2'); 273 | } 274 | }); 275 | }); 276 | -------------------------------------------------------------------------------- /test/material/depthMaterial.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { NullEngine } from '@babylonjs/core/Engines/nullEngine'; 18 | import { Material } from '@babylonjs/core/Materials/material'; 19 | import { MaterialDefines } from '@babylonjs/core/Materials/materialDefines'; 20 | import { UniformBuffer } from '@babylonjs/core/Materials/uniformBuffer'; 21 | import { Logger } from '@babylonjs/core/Misc/logger'; 22 | import { Scene } from '@babylonjs/core/scene'; 23 | import { Nullable } from '@babylonjs/core/types'; 24 | import { jest } from '@jest/globals'; 25 | 26 | import { DepthPluginMaterial } from '../../src/material/depthMaterial'; 27 | 28 | class MockMaterialDefines extends MaterialDefines { 29 | DEPTH_MAT: boolean = false; 30 | } 31 | 32 | Logger.LogLevels = Logger.ErrorLogLevel; 33 | 34 | describe('DepthPluginMaterial', () => { 35 | let engine: Nullable = null; 36 | let scene: Nullable = null; 37 | let material: Material; 38 | let depthPlugin: DepthPluginMaterial; 39 | let uniformBuffer: UniformBuffer; 40 | 41 | beforeEach(() => { 42 | engine = new NullEngine(); 43 | scene = new Scene(engine!); 44 | material = new Material('testMaterial', scene); 45 | depthPlugin = new DepthPluginMaterial(material); 46 | uniformBuffer = new UniformBuffer(engine!); 47 | }); 48 | 49 | afterEach(() => { 50 | uniformBuffer?.dispose(); 51 | depthPlugin?.dispose(); 52 | material?.dispose(); 53 | scene?.dispose(); 54 | engine?.dispose(); 55 | }); 56 | 57 | test('should initialize with default values', () => { 58 | expect(depthPlugin.darkness).toBe(1.0); 59 | expect(depthPlugin.gamma).toBe(1.0); 60 | expect(depthPlugin.enabled).toBe(false); 61 | }); 62 | 63 | test('should enable and disable the plugin', () => { 64 | depthPlugin.enabled = true; 65 | expect(depthPlugin.enabled).toBe(true); 66 | 67 | depthPlugin.enabled = false; 68 | expect(depthPlugin.enabled).toBe(false); 69 | }); 70 | 71 | test('should prepare defines based on the enabled state', () => { 72 | const defines = new MockMaterialDefines(); 73 | 74 | depthPlugin.prepareDefines(defines); 75 | expect(defines.DEPTH_MAT).toBe(false); 76 | 77 | depthPlugin.enabled = true; 78 | depthPlugin.prepareDefines(defines); 79 | expect(defines.DEPTH_MAT).toBe(true); 80 | }); 81 | 82 | test('should return correct uniforms', () => { 83 | const uniforms = depthPlugin.getUniforms(); 84 | expect(uniforms.ubo).toEqual([ 85 | { name: 'vdDarkness', size: 1, type: 'float' }, 86 | { name: 'vdGamma', size: 1, type: 'float' }, 87 | ]); 88 | expect(uniforms.fragment).toContain('uniform float vdDarkness;'); 89 | expect(uniforms.fragment).toContain('uniform float vdGamma;'); 90 | }); 91 | 92 | test('should update uniform buffer when enabled', () => { 93 | depthPlugin.enabled = true; 94 | uniformBuffer.updateFloat = jest.fn(); 95 | depthPlugin.bindForSubMesh(uniformBuffer); 96 | expect(uniformBuffer.updateFloat).toHaveBeenCalledWith('vdDarkness', 1.0); 97 | expect(uniformBuffer.updateFloat).toHaveBeenCalledWith('vdGamma', 1.0); 98 | }); 99 | 100 | test('should not update uniform buffer when disabled', () => { 101 | depthPlugin.enabled = false; 102 | uniformBuffer.updateFloat = jest.fn(); 103 | depthPlugin.bindForSubMesh(uniformBuffer); 104 | expect(uniformBuffer.updateFloat).not.toHaveBeenCalled(); 105 | }); 106 | 107 | test('should return a non-empty string for vertex shader', () => { 108 | const customCode = depthPlugin.getCustomCode('vertex'); 109 | expect(typeof customCode.CUSTOM_VERTEX_DEFINITIONS).toBe('string'); 110 | expect(customCode.CUSTOM_VERTEX_DEFINITIONS).not.toBe(''); 111 | expect(typeof customCode.CUSTOM_VERTEX_MAIN_END).toBe('string'); 112 | expect(customCode.CUSTOM_VERTEX_MAIN_END).not.toBe(''); 113 | }); 114 | 115 | test('should return a non-empty string for fragment shader', () => { 116 | const customCode = depthPlugin.getCustomCode('fragment'); 117 | expect(typeof customCode.CUSTOM_FRAGMENT_DEFINITIONS).toBe('string'); 118 | expect(customCode.CUSTOM_FRAGMENT_DEFINITIONS).not.toBe(''); 119 | expect(typeof customCode.CUSTOM_FRAGMENT_MAIN_BEGIN).toBe('string'); 120 | expect(customCode.CUSTOM_FRAGMENT_MAIN_BEGIN).not.toBe(''); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/model/model.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as fs from 'fs'; 18 | 19 | import { NullEngine } from '@babylonjs/core/Engines/nullEngine'; 20 | import { Vector3 } from '@babylonjs/core/Maths/math.vector'; 21 | import { Mesh } from '@babylonjs/core/Meshes/mesh'; 22 | import { Logger } from '@babylonjs/core/Misc/logger'; 23 | import { Node } from '@babylonjs/core/node'; 24 | import { Scene } from '@babylonjs/core/scene'; 25 | import { Nullable } from '@babylonjs/core/types'; 26 | 27 | import { ObservableManager } from '../../src/manager/observableManager'; 28 | import { SceneManager } from '../../src/manager/sceneManager'; 29 | import { Model } from '../../src/model/model'; 30 | import { ModelLoader } from '../../src/model/modelLoader'; 31 | import { Constant } from '../../src/util/index'; 32 | 33 | Logger.LogLevels = Logger.ErrorLogLevel; 34 | 35 | describe('Model', () => { 36 | let engine: Nullable = null; 37 | let scene: Nullable = null; 38 | let sceneManager: Nullable = null; 39 | let observableManager: Nullable = null; 40 | let modelLoader: Nullable = null; 41 | let model: Nullable = null; 42 | const modelName = 'mannequin'; 43 | 44 | beforeEach(async () => { 45 | engine = new NullEngine(); 46 | scene = new Scene(engine); 47 | observableManager = new ObservableManager(); 48 | sceneManager = new SceneManager(engine, {}, observableManager); 49 | await sceneManager.createScene(scene); 50 | modelLoader = new ModelLoader(sceneManager, observableManager); 51 | const data = fs.readFileSync('public/model/mannequin.glb').toString('base64'); 52 | model = await modelLoader!.loadGltf(`data:model/gltf-binary;base64,${data}`, true, modelName); 53 | }); 54 | 55 | afterEach(() => { 56 | model?.dispose(); 57 | modelLoader?.dispose(); 58 | sceneManager?.dispose(); 59 | observableManager?.dispose(); 60 | engine?.dispose(); 61 | }); 62 | 63 | test('getName', async () => { 64 | expect(model!.name).toEqual(modelName); 65 | }); 66 | 67 | test('removeFromScene', async () => { 68 | model?.showShadowOnGround(); 69 | 70 | model?.removeFromScene(); 71 | 72 | expect(sceneManager?.models.size).toBe(0); 73 | expect(sceneManager?.scene.meshes.length).toBe(0); 74 | }); 75 | 76 | test('toggleWireframe', async () => { 77 | // Turn on wireframe mode 78 | model!.toggleWireframe(true); 79 | let meshesWithWireframeOn = model!.rootMesh 80 | .getChildMeshes() 81 | .filter((mesh) => mesh.material && mesh.material.wireframe); 82 | expect(meshesWithWireframeOn.length).toBeGreaterThan(0); 83 | 84 | // Turn off wireframe mode 85 | model!.toggleWireframe(false); 86 | meshesWithWireframeOn = model!.rootMesh 87 | .getChildMeshes() 88 | .filter((mesh) => mesh.material && mesh.material.wireframe); 89 | expect(meshesWithWireframeOn.length).toBe(0); 90 | }); 91 | 92 | test('toggleVisibility', async () => { 93 | // Turn off visibility 94 | model?.toggleVisibility(false); 95 | expect(model?.rootMesh.isEnabled()).toBe(false); 96 | 97 | // Turn on visibility 98 | model?.toggleVisibility(); 99 | expect(model?.rootMesh.isEnabled()).toBe(true); 100 | }); 101 | 102 | test('toggleMatcapMode', async () => { 103 | // One pixel of #00000000 104 | const matcapTextureUrl = 105 | 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NgAAIAAAUAAR4f7BQAAAAASUVORK5CYII='; 106 | 107 | // Turn off matcap mode 108 | model?.toggleMatcapMode(matcapTextureUrl, false); 109 | let modelMaterials = model?.rootMesh 110 | .getChildMeshes() 111 | .filter((mesh) => mesh.material) 112 | .map((mesh) => mesh.material!.name); 113 | expect(modelMaterials!.includes(Constant.MATERIAL_CAPTURE_MATERIAL)).toBe(false); 114 | 115 | // Turn on matcap mode 116 | model?.toggleMatcapMode(matcapTextureUrl); 117 | modelMaterials = model?.rootMesh 118 | .getChildMeshes() 119 | .filter((mesh) => mesh.material) 120 | .map((mesh) => mesh.material!.name); 121 | expect(modelMaterials!.includes(Constant.MATERIAL_CAPTURE_MATERIAL)).toBe(true); 122 | let modelTextures = model?.rootMesh 123 | .getChildMeshes() 124 | .filter((mesh) => mesh.material) 125 | .map((mesh) => mesh.material!.getActiveTextures()[0].getInternalTexture()!.url); 126 | expect(modelTextures!.includes(matcapTextureUrl)).toBe(true); 127 | 128 | // Turn on matcap mode with forceEnabled 129 | model?.toggleMatcapMode(matcapTextureUrl, true); 130 | modelMaterials = model?.rootMesh 131 | .getChildMeshes() 132 | .filter((mesh) => mesh.material) 133 | .map((mesh) => mesh.material!.name); 134 | expect(modelMaterials!.includes(Constant.MATERIAL_CAPTURE_MATERIAL)).toBe(true); 135 | 136 | // Turn off matcap mode 137 | model?.toggleMatcapMode(matcapTextureUrl); 138 | modelMaterials = model?.rootMesh 139 | .getChildMeshes() 140 | .filter((mesh) => mesh.material) 141 | .map((mesh) => mesh.material!.name); 142 | expect(modelMaterials!.includes(Constant.MATERIAL_CAPTURE_MATERIAL)).toBe(false); 143 | 144 | // Test that the matcapTextureUrl can be swapped 145 | const fakeMatcapTextureUrl = 'fakeMatcapTextureUrl'; 146 | model?.toggleMatcapMode(fakeMatcapTextureUrl); 147 | modelTextures = model?.rootMesh 148 | .getChildMeshes() 149 | .filter((mesh) => mesh.material) 150 | .map((mesh) => mesh.material!.getActiveTextures()[0].getInternalTexture()!.url); 151 | expect(modelTextures!.includes('fakeMatcapTextureUrl')).toBe(true); 152 | }); 153 | 154 | test('toggleBoundingBox', async () => { 155 | // Turn on bounding box 156 | model!.toggleBoundingBox(true); 157 | let enabledBoundingBoxMeshes = model!.rootMesh 158 | .getChildMeshes() 159 | .filter((mesh) => mesh.name === 'BoundingBox' && mesh.isEnabled(false)); 160 | expect(enabledBoundingBoxMeshes.length).toBeGreaterThan(0); 161 | 162 | // Turn off bounding box 163 | model!.toggleBoundingBox(false); 164 | enabledBoundingBoxMeshes = model!.rootMesh 165 | .getChildMeshes() 166 | .filter((mesh) => mesh.name === 'BoundingBox' && mesh.isEnabled(false)); 167 | expect(enabledBoundingBoxMeshes.length).toBe(0); 168 | }); 169 | 170 | test('getOverallBoundingBox', async () => { 171 | const epsilon = 0.0001; 172 | const expectedMaximumWorld = new Vector3(0.2735, 1.773, 0.1647); 173 | const expectedMinimumWorld = new Vector3(-0.2735, 0, -0.1245); 174 | 175 | const overallBoundingBox = model!.getOverallBoundingBox(); 176 | 177 | expect(overallBoundingBox.maximumWorld.equalsWithEpsilon(expectedMaximumWorld, epsilon)).toBeTruthy(); 178 | expect(overallBoundingBox.minimumWorld.equalsWithEpsilon(expectedMinimumWorld, epsilon)).toBeTruthy(); 179 | }); 180 | 181 | test('getOverallBoundingBoxDimensions', async () => { 182 | const epsilon = 0.0001; 183 | const expectedDimensions = new Vector3(0.5469, 1.773, 0.2891); 184 | 185 | const overallBoundingBoxDimensions = model!.getOverallBoundingBoxDimensions(); 186 | 187 | expect(overallBoundingBoxDimensions.equalsWithEpsilon(expectedDimensions, epsilon)).toBeTruthy(); 188 | }); 189 | 190 | test('showShadowOnGround', async () => { 191 | model?.showShadowOnGround(); 192 | 193 | expect(sceneManager?.scene.getLightByName(Constant.DIRECTIONAL_SHADOW_LIGHT)).toBeTruthy(); 194 | expect(sceneManager?.scene.getMaterialByName(Constant.SHADOW_ONLY_MATERIAL)).toBeTruthy(); 195 | expect(sceneManager?.scene.getMeshByName(Constant.SHADOW_PLANE)).toBeTruthy(); 196 | }); 197 | 198 | test('showShadowOnGroundDepthMap', async () => { 199 | model?.showShadowOnGroundDepthMap(); 200 | 201 | expect(sceneManager?.scene.getCameraByName(Constant.DEPTH_MAP_SHADOW_CAMERA)).toBeTruthy(); 202 | expect(sceneManager?.scene.getMaterialByName(Constant.DEPTH_MAP_SHADOW_MATERIAL)).toBeTruthy(); 203 | expect(sceneManager?.scene.getMeshByName(Constant.DEPTH_MAP_SHADOW_PLANE)).toBeTruthy(); 204 | }); 205 | 206 | test('moveCenterToTargetCoordinate', async () => { 207 | model?.moveCenterToTargetCoordinate(Vector3.Zero()); 208 | const overallBoundingBox = model!.getOverallBoundingBox(); 209 | 210 | expect( 211 | overallBoundingBox.maximumWorld.add(overallBoundingBox.minimumWorld).equalsWithEpsilon(Vector3.Zero()), 212 | ).toBeTruthy(); 213 | }); 214 | 215 | test('hasMesh', async () => { 216 | expect(model?.hasMesh(new Mesh('mesh'))).toBeFalsy(); 217 | expect(model?.hasMesh(model?.rootMesh)).toBeTruthy(); 218 | }); 219 | 220 | test('hasNode', async () => { 221 | expect(model?.hasNode(new Node('node'))).toBeFalsy(); 222 | expect(model?.hasNode(model?.rootMesh)).toBeTruthy(); 223 | }); 224 | }); 225 | -------------------------------------------------------------------------------- /test/model/modelLoader.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as fs from 'fs'; 18 | 19 | import { NullEngine } from '@babylonjs/core/Engines/nullEngine'; 20 | import { Logger } from '@babylonjs/core/Misc/logger'; 21 | import { Scene } from '@babylonjs/core/scene'; 22 | import { Nullable } from '@babylonjs/core/types'; 23 | // @ts-ignore 24 | import nock from 'nock'; 25 | 26 | import { ObservableManager } from '../../src/manager/observableManager'; 27 | import { SceneManager } from '../../src/manager/sceneManager'; 28 | import { ModelLoader } from '../../src/model/modelLoader'; 29 | 30 | Logger.LogLevels = Logger.ErrorLogLevel; 31 | 32 | describe('ModelLoader', () => { 33 | let engine: Nullable = null; 34 | let scene: Nullable = null; 35 | let sceneManager: Nullable = null; 36 | let observableManager: Nullable = null; 37 | let modelLoader: Nullable = null; 38 | 39 | beforeEach(async () => { 40 | engine = new NullEngine(); 41 | scene = new Scene(engine); 42 | observableManager = new ObservableManager(); 43 | sceneManager = new SceneManager(engine, {}, observableManager); 44 | await sceneManager.createScene(scene); 45 | modelLoader = new ModelLoader(sceneManager, observableManager); 46 | }); 47 | 48 | afterEach(() => { 49 | nock.cleanAll(); 50 | modelLoader?.dispose(); 51 | sceneManager?.dispose(); 52 | observableManager?.dispose(); 53 | engine?.dispose(); 54 | }); 55 | 56 | it('should throw an error if empty url is provided', async () => { 57 | await expect(modelLoader?.loadGltf('', true)).rejects.toThrow(); 58 | }); 59 | 60 | it('should be able to load gltf asset using base64 data', async () => { 61 | const data = fs.readFileSync('public/model/mannequin.glb').toString('base64'); 62 | 63 | await modelLoader?.loadGltf(`data:model/gltf-binary;base64,${data}`, true); 64 | 65 | expect(sceneManager?.models.size).toBe(1); 66 | }); 67 | 68 | it('should be able to load glb asset using HTTP range requests', async () => { 69 | const data = fs.readFileSync('public/model/mannequin.glb'); 70 | nock('http://localhost') 71 | .get('/public/model/mannequin.glb') 72 | .matchHeader('range', 'bytes=0-19') 73 | .reply(206, data.subarray(0, 20), { 74 | 'content-Range': 'bytes 0-19/760664', 75 | 'content-length': '20', 76 | }) 77 | .get('/public/model/mannequin.glb') 78 | .matchHeader('range', 'bytes=20-2667') 79 | .reply(206, data.subarray(20, 2668), { 80 | 'content-Range': 'bytes 20-2667/760664', 81 | 'content-length': '2648', 82 | }) 83 | .get('/public/model/mannequin.glb') 84 | .matchHeader('range', 'bytes=2668-760663') 85 | .reply(206, data.subarray(2668, 760664), { 86 | 'content-Range': 'bytes 2668-760663/760664', 87 | 'content-length': '757996', 88 | }); 89 | 90 | await modelLoader?.loadGltf('http://localhost/public/model/mannequin.glb', true); 91 | 92 | expect(sceneManager?.models.size).toBe(1); 93 | }); 94 | 95 | it('should be able to load glb asset with full download when HTTP range requests failed', async () => { 96 | const data = fs.readFileSync('public/model/mannequin.glb'); 97 | nock('http://localhost') 98 | .get('/public/model/mannequin.glb') 99 | .matchHeader('range', 'bytes=0-19') 100 | .reply(206, Buffer.from(''), { 101 | 'content-Range': 'bytes 0-19/760664', 102 | 'content-length': '20', 103 | }) 104 | .get('/public/model/mannequin.glb') 105 | .reply(200, data); 106 | 107 | await modelLoader?.loadGltf('http://localhost/public/model/mannequin.glb', true); 108 | 109 | expect(sceneManager?.models.size).toBe(1); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/scene/defaultScene.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as fs from 'fs'; 18 | 19 | import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera'; 20 | import { Logger } from '@babylonjs/core/Misc/logger'; 21 | import { Tools } from '@babylonjs/core/Misc/tools'; 22 | import { Nullable } from '@babylonjs/core/types'; 23 | import { jest } from '@jest/globals'; 24 | 25 | import { Config } from '../../src/config/config'; 26 | import { DEFAULT_CONFIG } from '../../src/config/preset/defaultConfig'; 27 | import { DefaultScene } from '../../src/scene/defaultScene'; 28 | 29 | Logger.LogLevels = Logger.ErrorLogLevel; 30 | 31 | describe('DefaultScene', () => { 32 | let defaultScene: Nullable = null; 33 | const lightingData = 'data;,' + fs.readFileSync('public/ibl/Neutral.env').toString('base64'); 34 | 35 | beforeEach(() => { 36 | const presetConfig: Config = { 37 | '3dCommerceCertified': true, 38 | engineConfig: { 39 | useNullEngine: true, 40 | }, 41 | enableDragAndDrop: true, 42 | sceneConfig: { 43 | useLoadingUI: true, 44 | }, 45 | lightingConfig: { 46 | Neutral: { 47 | type: 'env', 48 | enable: false, 49 | }, 50 | }, 51 | }; 52 | defaultScene = new DefaultScene(null, presetConfig); 53 | }); 54 | 55 | afterEach(() => { 56 | defaultScene?.dispose(); 57 | }); 58 | 59 | test('init', async () => { 60 | await defaultScene?.init(); 61 | 62 | expect(defaultScene?.engine).toBeTruthy(); 63 | expect(defaultScene?.scene).toBeTruthy(); 64 | expect(defaultScene?.sceneManager).toBeTruthy(); 65 | expect(defaultScene?.config).toBeTruthy(); 66 | expect(defaultScene?.observableManager).toBeTruthy(); 67 | expect(defaultScene?.canvas).toBeFalsy(); 68 | }); 69 | 70 | test('should use DEFAULT_CONFIG if presetConfig is not provided', async () => { 71 | defaultScene = new DefaultScene(null); 72 | 73 | expect((defaultScene as any)._initialConfig).toEqual(DEFAULT_CONFIG); 74 | }); 75 | 76 | test('updateConfig', async () => { 77 | await defaultScene?.init(); 78 | 79 | await defaultScene?.updateConfig({ 80 | lightingConfig: { 81 | DirectionalLight: { 82 | type: 'directional', 83 | }, 84 | }, 85 | }); 86 | 87 | expect(defaultScene?.scene.getLightByName('DirectionalLight')).toBeTruthy(); 88 | }); 89 | 90 | test('loadGltf', async () => { 91 | await defaultScene?.init(); 92 | const data = fs.readFileSync('public/model/mannequin.glb').toString('base64'); 93 | 94 | await defaultScene?.loadGltf(`data:model/gltf-binary;base64,${data}`, true); 95 | 96 | expect(defaultScene?.sceneManager.models.size).toBe(1); 97 | }); 98 | 99 | test('loadGltfFromFiles', async () => { 100 | await defaultScene?.init(); 101 | const data = fs.readFileSync('public/model/mannequin.glb'); 102 | const blob = new Blob([data.buffer]); 103 | const file = new File([blob], 'mannequin.glb'); 104 | 105 | defaultScene?.loadGltfFromFiles([file]); 106 | 107 | await new Promise((resolve) => setTimeout(resolve, 2000)); 108 | expect(defaultScene?.sceneManager.models.size).toBe(1); 109 | }); 110 | 111 | it('should invoke Tools.CreateScreenshotAsync() when usingRenderTarget is false', async () => { 112 | const mockCreateScreenshot = jest.fn(); 113 | const mockCreateScreenshotUsingRenderTarget = jest.fn(); 114 | Tools.CreateScreenshotAsync = mockCreateScreenshot; 115 | Tools.CreateScreenshotUsingRenderTargetAsync = mockCreateScreenshotUsingRenderTarget; 116 | await defaultScene?.init(); 117 | 118 | await defaultScene!.takeScreenshot(600, false); 119 | 120 | expect(mockCreateScreenshot).toHaveBeenCalledTimes(1); 121 | expect(mockCreateScreenshotUsingRenderTarget).toHaveBeenCalledTimes(0); 122 | }); 123 | 124 | it('should invoke Tools.CreateScreenshotUsingRenderTargetAsync() when usingRenderTarget is true', async () => { 125 | const mockCreateScreenshot = jest.fn(); 126 | const mockCreateScreenshotUsingRenderTarget = jest.fn(); 127 | Tools.CreateScreenshotAsync = mockCreateScreenshot; 128 | Tools.CreateScreenshotUsingRenderTargetAsync = mockCreateScreenshotUsingRenderTarget; 129 | await defaultScene?.init(); 130 | 131 | await defaultScene!.takeScreenshot(600, true); 132 | 133 | expect(mockCreateScreenshot).toHaveBeenCalledTimes(0); 134 | expect(mockCreateScreenshotUsingRenderTarget).toHaveBeenCalledTimes(1); 135 | }); 136 | 137 | test('toggleLighting', async () => { 138 | await defaultScene?.init(); 139 | await defaultScene?.updateConfig({ 140 | lightingConfig: { 141 | NeutralIBL: { 142 | type: 'env', 143 | enable: true, 144 | filePath: lightingData, 145 | }, 146 | }, 147 | }); 148 | 149 | // Turn off NeutralIBL 150 | defaultScene?.toggleLighting('NeutralIBL', false); 151 | expect(defaultScene?.scene.environmentTexture).toBeFalsy(); 152 | 153 | // Turn on NeutralIBL 154 | defaultScene?.toggleLighting('NeutralIBL', true); 155 | expect(defaultScene?.scene.environmentTexture).toBeTruthy(); 156 | }); 157 | 158 | test('zoomOnModels', async () => { 159 | await defaultScene?.init(); 160 | await defaultScene?.updateConfig({ 161 | cameraConfig: { 162 | ArcRotateCamera: { 163 | type: 'arcRotateCamera', 164 | enable: true, 165 | framingBehavior: { 166 | enabled: true, 167 | }, 168 | }, 169 | }, 170 | }); 171 | const camera = defaultScene?.scene.activeCamera as ArcRotateCamera; 172 | const initCameraRadius = camera.radius; 173 | const data = fs.readFileSync('public/model/mannequin.glb').toString('base64'); 174 | const model = await defaultScene?.loadGltf(`data:model/gltf-binary;base64,${data}`, true); 175 | 176 | defaultScene?.zoomOnModelsWithFramingBehavior([model!]); 177 | 178 | expect(camera.radius).not.toBe(initCameraRadius); 179 | }); 180 | 181 | test('get camera by name', async () => { 182 | await defaultScene?.init(); 183 | await defaultScene?.updateConfig({ 184 | cameraConfig: { 185 | ArcRotateCamera: { 186 | type: 'arcRotateCamera', 187 | enable: true, 188 | }, 189 | }, 190 | }); 191 | const camera = defaultScene?.getCamera('ArcRotateCamera'); 192 | 193 | expect(camera).toBe(defaultScene?.scene.activeCamera); 194 | }); 195 | }); 196 | -------------------------------------------------------------------------------- /test/scene/v3dScene.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as fs from 'fs'; 18 | 19 | import { Logger } from '@babylonjs/core/Misc/logger'; 20 | import { Nullable } from '@babylonjs/core/types'; 21 | 22 | import { Config } from '../../src/config/config'; 23 | import { V3DScene } from '../../src/scene/v3dScene'; 24 | 25 | Logger.LogLevels = Logger.ErrorLogLevel; 26 | 27 | describe('V3DScene', () => { 28 | let v3dScene: Nullable = null; 29 | const lightingData = 'data;,' + fs.readFileSync('public/ibl/Neutral.env').toString('base64'); 30 | 31 | beforeEach(() => { 32 | const presetConfig: Config = { 33 | '3dCommerceCertified': true, 34 | engineConfig: { 35 | useNullEngine: true, 36 | }, 37 | lightingConfig: { 38 | Neutral: { 39 | type: 'env', 40 | enable: true, 41 | filePath: lightingData, 42 | }, 43 | }, 44 | }; 45 | v3dScene = new V3DScene(null, presetConfig); 46 | }); 47 | 48 | afterEach(() => { 49 | v3dScene?.dispose(); 50 | }); 51 | 52 | test('init', async () => { 53 | await v3dScene?.init(); 54 | 55 | expect(v3dScene?.engine).toBeTruthy(); 56 | expect(v3dScene?.scene).toBeTruthy(); 57 | expect(v3dScene?.sceneManager).toBeTruthy(); 58 | expect(v3dScene?.config).toBeTruthy(); 59 | expect(v3dScene?.observableManager).toBeTruthy(); 60 | expect(v3dScene?.canvas).toBeFalsy(); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/util/convexHull.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Vector3 } from '@babylonjs/core/Maths/math.vector'; 18 | 19 | import { ConvexHull } from '../../src/util/convexHull'; 20 | 21 | describe('ConvexHull', () => { 22 | const allVertices = [ 23 | new Vector3(0, 0, 0), 24 | new Vector3(1, 0, 0), 25 | new Vector3(1, 1, 0), 26 | new Vector3(0, 0, 1), 27 | new Vector3(0.15, 0.15, 0.1), 28 | new Vector3(0.5, 0.5, 0.5), 29 | ]; 30 | 31 | test('getFaces', async () => { 32 | const faces = ConvexHull.getFaces(allVertices); 33 | 34 | expect(faces.length).toEqual(4); 35 | }); 36 | 37 | test('getVertexIndices', async () => { 38 | const vertexIndices = ConvexHull.getVertexIndices(allVertices); 39 | 40 | expect(vertexIndices.length).toEqual(4); 41 | for (let i = 0; i < 4; i++) { 42 | expect(vertexIndices.includes(i)).toBeTruthy(); 43 | } 44 | }); 45 | 46 | test('getVertices', async () => { 47 | const vertices = ConvexHull.getVertices(allVertices); 48 | 49 | expect(vertices.length).toEqual(4); 50 | for (let i = 0; i < 4; i++) { 51 | expect(allVertices.includes(vertices[i])).toBeTruthy(); 52 | } 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/util/index.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as fs from 'fs'; 18 | 19 | import { Logger } from '@babylonjs/core/Misc/logger'; 20 | import { Nullable } from '@babylonjs/core/types'; 21 | 22 | import { Config } from '../../src/config/config'; 23 | import { DefaultScene } from '../../src/scene/defaultScene'; 24 | import { Constant, Util } from '../../src/util/index'; 25 | 26 | Logger.LogLevels = Logger.ErrorLogLevel; 27 | 28 | describe('Lighting', () => { 29 | describe('isTextureAsset', () => { 30 | it('should return true if file extension is .png', () => { 31 | const filename = 'http://localhost/test.png?param'; 32 | 33 | const isTextureAsset = Util.isTextureAsset(filename); 34 | 35 | expect(isTextureAsset).toBeTruthy(); 36 | }); 37 | 38 | it('should return false if file extension is .glb', () => { 39 | const filename = 'test.glb'; 40 | 41 | const isTextureAsset = Util.isTextureAsset(filename); 42 | 43 | expect(isTextureAsset).toBeFalsy(); 44 | }); 45 | }); 46 | }); 47 | 48 | describe('Util.createMetallicSphere', () => { 49 | const data = fs.readFileSync('public/model/mannequin.glb').toString('base64'); 50 | let defaultScene: Nullable = null; 51 | 52 | beforeEach(() => { 53 | const presetConfig: Config = { 54 | engineConfig: { 55 | useNullEngine: true, 56 | }, 57 | }; 58 | defaultScene = new DefaultScene(null, presetConfig); 59 | }); 60 | 61 | afterEach(() => { 62 | defaultScene?.dispose(); 63 | }); 64 | 65 | it('should return null if ', async () => { 66 | const metallicSphere = Util.createMetallicSphere(defaultScene!); 67 | await new Promise((resolve) => setTimeout(resolve, 1000)); 68 | 69 | expect(metallicSphere).toBe(null); 70 | }); 71 | 72 | it('should be able to create metallic sphere mesh', async () => { 73 | await defaultScene!.init(); 74 | await defaultScene!.loadGltf(`data:model/gltf-binary;base64,${data}`, true); 75 | 76 | Util.createMetallicSphere(defaultScene!); 77 | await new Promise((resolve) => setTimeout(resolve, 1000)); 78 | 79 | expect(defaultScene!.sceneManager.scene.getMeshByName(Constant.LIGHTING_REFLECTION_SPHERE)).toBeTruthy(); 80 | expect(defaultScene!.sceneManager.scene.getMaterialByName(Constant.METALLIC_SPHERE_MATERIAL)).toBeTruthy(); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "esModuleInterop": true, 5 | "importHelpers": true, 6 | "moduleResolution": "node", 7 | "module": "ES2020", 8 | "target": "ES6", 9 | "downlevelIteration": true, 10 | "declaration": true, 11 | "sourceMap": true, 12 | "inlineSources": true, 13 | "experimentalDecorators": true, 14 | "noImplicitAny": true, 15 | "noImplicitReturns": true, 16 | "noImplicitThis": true, 17 | "noUnusedLocals": true, 18 | "strictNullChecks": true, 19 | "strictFunctionTypes": true, 20 | "skipLibCheck": true, 21 | "removeComments": false, 22 | "jsx": "react-jsx", 23 | "lib": [ 24 | "es5", 25 | "dom", 26 | "es2015.promise", 27 | "es2015.collection", 28 | "es2015.iterable", 29 | "es2015.symbol.wellknown" 30 | ], 31 | "outDir": "./dist", 32 | "rootDir": "./src" 33 | }, 34 | "typedocOptions": { 35 | "entryPoints": ["src"], 36 | "entryPointStrategy": "expand", 37 | "exclude": "**/*+(index|.d).ts", 38 | "excludeReferences": true, 39 | "out": "docs" 40 | }, 41 | "include": ["./src/**/*"], 42 | "exclude": ["./src/dev/**/*", "**/node_modules", "**/dist", "**/web"] 43 | } -------------------------------------------------------------------------------- /webpack.config.mjs: -------------------------------------------------------------------------------- 1 | import CopyWebpackPlugin from 'copy-webpack-plugin'; 2 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 3 | import { fileURLToPath } from 'url'; 4 | import path from 'path'; 5 | 6 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 7 | 8 | export default (env, argv) => { 9 | const isProd = env.production || false; 10 | 11 | const plugins = [ 12 | new HtmlWebpackPlugin({ 13 | template: './src/dev/viewer.html', 14 | filename: 'index.html', 15 | chunks: ['index'], 16 | }), 17 | new HtmlWebpackPlugin({ 18 | template: './src/dev/viewer.html', 19 | filename: 'v3dViewer.html', 20 | chunks: ['v3dViewer'], 21 | }), 22 | new CopyWebpackPlugin({ 23 | patterns: [ 24 | { from: 'public', to: 'public' }, 25 | { from: 'docs', to: 'docs', noErrorOnMissing: true }, 26 | ], 27 | }), 28 | ]; 29 | 30 | return { 31 | mode: isProd ? 'production' : 'development', 32 | devtool: isProd ? false : 'eval-cheap-module-source-map', 33 | entry: { 34 | index: './src/dev/index.ts', 35 | v3dViewer: './src/dev/v3dViewer.ts', 36 | }, 37 | output: { 38 | path: path.resolve(__dirname, 'web'), 39 | filename: '[name].bundle.js', 40 | clean: true, 41 | }, 42 | optimization: { 43 | splitChunks: { 44 | chunks: 'all', 45 | }, 46 | }, 47 | resolve: { 48 | extensions: ['.tsx', '.js', '.ts'], 49 | }, 50 | module: { 51 | rules: [ 52 | { 53 | test: /\.css$/, 54 | use: ['style-loader', 'css-loader'], 55 | }, 56 | { 57 | test: /\.(png|svg|jpg|gif|gltf|bin|env|peg|woff|woff2|eot|ttf)$/, 58 | use: ['file-loader'], 59 | }, 60 | { 61 | test: /\.tsx?$/, 62 | loader: 'ts-loader', 63 | }, 64 | ], 65 | }, 66 | devServer: { 67 | static: [{ 68 | directory: path.resolve(__dirname, 'public'), 69 | publicPath: '/public', 70 | }, { 71 | directory: path.resolve(__dirname, 'docs'), 72 | publicPath: '/docs', 73 | }], 74 | compress: false, 75 | open: 'index.html', 76 | port: 8443, 77 | server: 'http', 78 | hot: true, 79 | liveReload: true, 80 | historyApiFallback: false, 81 | headers: { 82 | 'Access-Control-Allow-Origin': '*', 83 | }, 84 | }, 85 | plugins: plugins, 86 | }; 87 | }; 88 | --------------------------------------------------------------------------------