├── .eslintrc.json ├── .github ├── pull_request_template.md └── workflows │ ├── activity-count.yml │ ├── npm-publish.yml │ ├── npm-run.yml │ └── readme-to-docs.yml ├── .gitignore ├── .gitmodules ├── .prettierrc.json ├── .vscode └── settings.json ├── CODE_OF_CONDUCT ├── CONTRIBUTING ├── LICENSE ├── README.md ├── __tests__ ├── checkSubmoduleTesting.test.js ├── examplesTesting.test.js └── loadModelsTesting.test.js ├── dist ├── 57cf7fc5ff4d6dfc74e4.module.wasm ├── mr.js └── vendors-node_modules_dimforge_rapier3d_rapier_js.mr.js ├── jest.config.js ├── jsdoc.config.json ├── package-lock.json ├── package.json ├── samples ├── examples-assets │ ├── audio │ │ ├── rain-loop.wav │ │ ├── thunder-clap1.mp3 │ │ └── thunder-clap2.wav │ ├── fonts │ │ ├── BricolageGrotesque.ttf │ │ ├── BricolageGrotesque.woff │ │ ├── Roboto-Regular.ttf │ │ └── Roboto-Regular.woff │ ├── js │ │ └── SpinSystem.js │ ├── models │ │ ├── Basecolor_0.001.jpg │ │ ├── Basecolor_0.jpg │ │ ├── Mars.glb │ │ ├── animation_koifish.glb │ │ ├── blue_whale.glb │ │ ├── dae │ │ │ ├── SpiderPlant_Base_color.png │ │ │ ├── attribution.txt │ │ │ └── spiderPlant.dae │ │ ├── daffodils_island.glb │ │ ├── door1-UI.glb │ │ ├── door1.glb │ │ ├── fbx │ │ │ ├── FourthGreen.png │ │ │ ├── animatedChick.fbx │ │ │ └── attribution.txt │ │ ├── glb │ │ │ ├── attribution.txt │ │ │ └── cassetteTape.glb │ │ ├── great_hammerhead_shark.glb │ │ ├── island3.dae │ │ ├── island3.glb │ │ ├── koifish.dae │ │ ├── koifish.mtl │ │ ├── koifish.obj │ │ ├── logo.bin │ │ ├── logo.dae │ │ ├── logo.fbx │ │ ├── logo.glb │ │ ├── logo.gltf │ │ ├── logo.ply │ │ ├── logo.stl │ │ ├── logo.usdc │ │ ├── logo.usdz │ │ ├── logo_nomtl.obj │ │ ├── obj1 │ │ │ ├── attribution.txt │ │ │ └── cow.obj │ │ ├── obj2 │ │ │ ├── attribution.txt │ │ │ ├── board n knife Base Color.jpg │ │ │ ├── food Base Color.jpg │ │ │ ├── stillLife.mtl │ │ │ └── stillLife.obj │ │ ├── poof1.glb │ │ └── tiledesert001.glb │ └── textures │ │ ├── Water_1_M_Flow.jpg │ │ ├── Water_1_M_Normal.jpg │ │ ├── Water_2_M_Normal.jpg │ │ ├── bump.jpg │ │ ├── founder-shot.jpeg │ │ ├── headset-keyboard.jpeg │ │ ├── night_life.jpeg │ │ ├── simple.webm │ │ ├── skybox_daytime │ │ ├── nx.jpg │ │ ├── ny.jpg │ │ ├── nz.jpg │ │ ├── px.jpg │ │ ├── py.jpg │ │ └── pz.jpg │ │ ├── skybox_hdri.jpg │ │ ├── skybox_milkyway.jpg │ │ ├── skybox_starmap_4k.jpg │ │ ├── skybox_surreal.jpg │ │ └── skybox_watercolor01.jpg ├── examples │ ├── anchors-style.css │ ├── anchors.html │ ├── audio-style.css │ ├── audio.html │ ├── camera-style.css │ ├── camera.html │ ├── debug-style.css │ ├── debug.html │ ├── embed-style.css │ ├── embed.html │ ├── images-style.css │ ├── images.html │ ├── models-style.css │ ├── models.html │ ├── panels-style.css │ ├── panels.html │ ├── physics.html │ ├── skybox-style.css │ ├── skybox.html │ ├── text-style.css │ ├── text.html │ ├── video-style.css │ └── video.html ├── favicon.svg ├── index-assets │ ├── BricolageGrotesque.ttf │ ├── BricolageGrotesque96pt-SemiBold.ttf │ ├── Onest-Bold.ttf │ ├── Onest-Regular.ttf │ ├── all-in-one.glb │ ├── bowtie.glb │ ├── familiar.glb │ ├── jigsaw.glb │ ├── koifish.glb │ ├── mrjs_logo.glb │ ├── mrjs_logo_large.glb │ └── puzzle.glb ├── index-style.css ├── index.html └── opengraph.jpg ├── scripts ├── check-and-update-submodule.sh ├── check-if-submodule-needs-update.sh ├── create-docs.sh └── update-all-submodules.sh ├── src ├── core │ ├── MRApp.js │ ├── MRElement.js │ ├── MREntity.js │ ├── MRSystem.js │ ├── componentSystems │ │ ├── AnchorSystem.js │ │ ├── AnimationSystem.js │ │ ├── AudioSystem.js │ │ ├── BoundaryVisibilitySystem.js │ │ ├── ClippingSystem.js │ │ ├── ControlSystem.js │ │ ├── GeometryStyleSystem.js │ │ ├── InstancingSystem.js │ │ ├── LayoutSystem.js │ │ ├── MaskingSystem.js │ │ ├── MaterialStyleSystem.js │ │ ├── PanelSystem.js │ │ ├── PhysicsSystem.js │ │ ├── SkyBoxSystem.js │ │ ├── StatsSystem.js │ │ └── TextSystem.js │ ├── entities │ │ ├── MRButtonEntity.js │ │ ├── MRDivEntity.js │ │ ├── MRHyperlinkEntity.js │ │ ├── MRImageEntity.js │ │ ├── MRLightEntity.js │ │ ├── MRMediaEntity.js │ │ ├── MRModelEntity.js │ │ ├── MRPanelEntity.js │ │ ├── MRSkyBoxEntity.js │ │ ├── MRStatsEntity.js │ │ ├── MRTextAreaEntity.js │ │ ├── MRTextEntity.js │ │ ├── MRTextFieldEntity.js │ │ ├── MRTextInputEntity.js │ │ ├── MRVideoEntity.js │ │ └── MRVolumeEntity.js │ └── user │ │ ├── MRHand.js │ │ └── MRUser.js ├── dataManagers │ └── MRPlaneManager.js ├── dataTypes │ ├── MRClippingGeometry.js │ └── MRPlane.js ├── defaultStyle.css ├── extras │ ├── Refractor.js │ ├── Water.js │ └── index.js ├── global.js ├── index.js └── utils │ ├── App.js │ ├── CSS.js │ ├── Color.js │ ├── Display.js │ ├── Geometry.js │ ├── HTML.js │ ├── JS.js │ ├── Material.js │ ├── Math.js │ ├── Model.js │ ├── Notify.js │ ├── Physics.js │ ├── String.js │ ├── XR.js │ └── index.js └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": "latest", 9 | "sourceType": "module" 10 | }, 11 | "extends": ["plugin:jsdoc/recommended"], 12 | "plugins": ["jsdoc"], 13 | "rules": { 14 | // "no-undef": "warn", // useful when debuggin 15 | // "no-unused-vars": "warn", // useful when debuggin 16 | "jsdoc/require-jsdoc": [ 17 | "error", 18 | { 19 | "require": { 20 | "FunctionDeclaration": true, 21 | "MethodDefinition": true, 22 | "ClassDeclaration": true 23 | // You can add more as needed: ArrowFunctionExpression, FunctionExpression, etc. 24 | } 25 | } 26 | ], 27 | "max-len": ["error", { "code": 180 }], // Make sure this matches prettier 28 | "no-var": "error" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Linking 2 | 3 | Options: 4 | 5 | - *From: Link to whatever issue comment started this* 6 | - *Fixes #issue* 7 | - *Related to #issue* 8 | 9 | ## Problem 10 | 11 | *Description of the problem including potential code and/or screenshots as an example* 12 | 13 | ## Solution 14 | 15 | *Quick explanation of change to be done* 16 | 17 | ### Breaking Change 18 | 19 | *If this is a breaking change describe the before and after and why the change was necessary* 20 | 21 | ## Notes 22 | 23 | *Notes and any associated research or links* 24 | 25 | ------------ 26 | 27 | ## Required to Merge 28 | 29 | - [ ] **PASS** - all necessary actions must pass (excluding the auto-skipped ones) 30 | - [ ] **TEST IN HEADSET** - [main dev-testing-example](https://github.com/Volumetrics-io/mrjs/tree/main/samples/index.html) and any of the other [examples](https://github.com/Volumetrics-io/mrjs/tree/main/samples/examples) still work as expected 31 | - [ ] **VIDEO** - if this pr changes something visually - post a video here of it in headset-MR and/or on desktop (depending on what it affects) for the reviewer to reference. 32 | - [ ] **TITLE** - make sure the pr's title is updated appropriately as it will be used to name the commit on merge 33 | - [ ] **BREAKING CHANGE** 34 | - **DOCUMENTATION**: This includes any changes to html tags and their components 35 | - make a pr in the [documentation repo](https://github.com/Volumetrics-io/documentation) that updates the manual docs to match the breaking change 36 | - link the pr of the documentation repo here: *#pr* 37 | - that pr must be approved by `@lobau` 38 | - **SAMPLES/INDEX.HTML**: This includes any changes (html tags or otherwise) that must be done to our landing page submodule as an effect of this pr's updates 39 | - make a pr in the [mrjs landing page repo](https://github.com/Volumetrics-io/mrjs-landing) that updates the landing page to match the breaking change 40 | - link the pr of the landing page repo here: *#pr* 41 | - that pr must be approved by `@hanbollar` 42 | -------------------------------------------------------------------------------- /.github/workflows/activity-count.yml: -------------------------------------------------------------------------------- 1 | name: count activity by non-members this month 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | count-activity: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@v4 13 | 14 | - name: Count issues and PRs activity by non-members with pagination 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | run: | 18 | # Function to paginate through API results 19 | function paginate() { 20 | url=$1 21 | all_data="[]" 22 | while [ -n "$url" ]; do 23 | response=$(curl -H "Authorization: token $GITHUB_TOKEN" -s -I "$url") 24 | data=$(curl -H "Authorization: token $GITHUB_TOKEN" -s "$url") 25 | all_data=$(echo "$all_data$data" | jq -s add) 26 | next_link=$(echo "$response" | grep -oP '(?<=<)(.*?)(?=>; rel="next")') 27 | url=${next_link:+$next_link} 28 | done 29 | echo "$all_data" 30 | } 31 | 32 | # Get the first day of the current month 33 | month_start=$(date -u +"%Y-%m-01T00:00:00Z") 34 | 35 | # Pagination for opened issues 36 | opened_issues_data=$(paginate "https://api.github.com/repos/${{ github.repository }}/issues?state=open&since=$month_start") 37 | opened_issues_count=$(echo "$opened_issues_data" | jq '[.[] | select(.author_association != "MEMBER") and select(.author_association != "OWNER") and (.pull_request == null)] | length') 38 | 39 | # Pagination for opened PRs 40 | opened_prs_data=$(paginate "https://api.github.com/repos/${{ github.repository }}/pulls?state=open&since=$month_start") 41 | opened_prs_count=$(echo "$opened_prs_data" | jq '[.[] | select(.author_association != "MEMBER") and select(.author_association != "OWNER")] | length') 42 | 43 | # Pagination for closed issues 44 | closed_issues_data=$(paginate "https://api.github.com/repos/${{ github.repository }}/issues?state=closed") 45 | closed_issues_count=$(echo "$closed_issues_data" | jq '[.[] | select(.closed_at >= "'$month_start'") and select(.author_association != "MEMBER") and select(.author_association != "OWNER") and (.pull_request == null)] | length') 46 | 47 | # Pagination for closed PRs 48 | closed_prs_data=$(paginate "https://api.github.com/repos/${{ github.repository }}/pulls?state=closed") 49 | closed_prs_count=$(echo "$closed_prs_data" | jq '[.[] | select(.closed_at >= "'$month_start'") and select(.author_association != "MEMBER") and select(.author_association != "OWNER")] | length') 50 | 51 | echo "Number of issues opened by non-members this month: $opened_issues_count" 52 | echo "Number of PRs opened by non-members this month: $opened_prs_count" 53 | echo "Number of issues closed by non-members this month: $closed_issues_count" 54 | echo "Number of PRs closed by non-members this month: $closed_prs_count" 55 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: npm-publish 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - 'package.json' 8 | 9 | jobs: 10 | npm-publish: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 # Fetch all history for all tags and branches 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: 21 21 | 22 | - name: Get version from package.json 23 | id: package_version 24 | run: echo "VERSION=$(jq -r .version package.json)" >> $GITHUB_ENV 25 | 26 | - name: Check if the tag exists 27 | id: tag_check 28 | run: | 29 | if git rev-parse "v${{ env.VERSION }}" >/dev/null 2>&1; then 30 | echo "EXISTS=true" >> $GITHUB_ENV 31 | else 32 | echo "EXISTS=false" >> $GITHUB_ENV 33 | fi 34 | 35 | - name: Install Dependencies 36 | if: env.EXISTS == 'false' 37 | run: | 38 | npm install 39 | 40 | - name: 👷 Build again just in case 41 | if: env.EXISTS == 'false' 42 | run: | 43 | rm -rf ./dist 44 | npm run build 45 | 46 | - name: Commit the clean build for the tag 47 | if: env.EXISTS == 'false' 48 | run: | 49 | git config user.name 'github-actions[bot]' 50 | git config user.email 'github-actions[bot]@users.noreply.github.com' 51 | commit_message=$'👷 MRjs Publish - Auto Dist For ${{ env.VERSION }} 👷\n\nChanges at '"${GITHUB_SHA}" 52 | git add . 53 | git commit -m "$commit_message" 54 | 55 | - name: Create Git tag 56 | if: env.EXISTS == 'false' 57 | run: | 58 | git config user.name 'github-actions[bot]' 59 | git config user.email 'github-actions[bot]@users.noreply.github.com' 60 | git tag v${{ env.VERSION }} 61 | git push origin v${{ env.VERSION }} 62 | 63 | - name: Debug token availability 64 | if: env.EXISTS == 'false' 65 | run: | 66 | echo "Token length: ${#NPM_MJRS_PUBLISH}" 67 | 68 | - name: Publish to npm 69 | if: env.EXISTS == 'false' 70 | uses: JS-DevTools/npm-publish@v3 71 | with: 72 | token: ${{ secrets.NPM_MJRS_PUBLISH }} 73 | -------------------------------------------------------------------------------- /.github/workflows/readme-to-docs.yml: -------------------------------------------------------------------------------- 1 | # Auto generate docs as a new commit for the docs repo 2 | name: update readme to docs 3 | 4 | on: 5 | workflow_dispatch: 6 | push: 7 | branches: 8 | - main 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | readme-update-to-docs: 15 | runs-on: macos-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Get README content 20 | id: get-readme 21 | run: | 22 | cp README.md ../new-src-index.md 23 | 24 | - name: see if info is there 25 | run: | 26 | echo "ll .." 27 | ls -al .. 28 | echo "ll ." 29 | ls -al . 30 | 31 | - name: add proper description to top of markdown file 32 | run: | 33 | FILE="../new-src-index.md" 34 | TEXT='hi.txt' 35 | echo '---' >> $TEXT 36 | echo 'description: "An extendable WebComponents library for the Spatial Web"' >> $TEXT 37 | echo '---' >> $TEXT 38 | cat $TEXT $FILE > temp && mv temp $FILE 39 | echo "
Suggest an edit on GitHub for README.md
" >> "$FILE" 40 | cat $FILE 41 | 42 | - name: Checkout documentation Repository 43 | uses: actions/checkout@v4 44 | with: 45 | repository: Volumetrics-io/documentation 46 | ref: main 47 | token: ${{ secrets.MRJS_AND_DOCS_REPO_PAT }} 48 | 49 | - name: Update Docs 50 | run: | 51 | rm "./source/index.md" 52 | mv -f "../new-src-index.md" "./source/index.md" 53 | 54 | - name: Commit only if there are changes 55 | run: | 56 | if [[ -n $(git diff --exit-code) ]]; then 57 | echo "Changes detected. Committing and pushing." 58 | git config user.name 'github-actions[bot]' 59 | git config user.email 'github-actions[bot]@users.noreply.github.com' 60 | commit_message=$'👷 MRjs - Auto Generated README to Index Update 👷\n\nChanges at '"${GITHUB_SHA}" 61 | git add . 62 | git commit -m "$commit_message" 63 | git push --quiet --set-upstream origin HEAD --force 64 | else 65 | echo "No changes detected. Exiting without committing." 66 | fi 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.MRJS_AND_DOCS_REPO_PAT }} 69 | 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build/Dist - ignore in this repo, they'll be built directly by render.com 2 | dist/examples-assets* 3 | dist/examples* 4 | dist/samples* 5 | dist/sites* 6 | dist/index* 7 | dist/style* 8 | 9 | 10 | # Docs - ignore auto html docs in this repo, they'll be exported to the docs repo 11 | docs/* 12 | 13 | # Logs 14 | logs 15 | *.log 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | lerna-debug.log* 20 | 21 | *.pem 22 | 23 | # Diagnostic reports (https://nodejs.org/api/report.html) 24 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 25 | 26 | # Runtime data 27 | pids 28 | *.pid 29 | *.seed 30 | *.pid.lock 31 | 32 | # Directory for instrumented libs generated by jscoverage/JSCover 33 | lib-cov 34 | 35 | # Coverage directory used by tools like istanbul 36 | coverage 37 | *.lcov 38 | 39 | # nyc test coverage 40 | .nyc_output 41 | 42 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | bower_components 47 | 48 | # node-waf configuration 49 | .lock-wscript 50 | 51 | # Compiled binary addons (https://nodejs.org/api/addons.html) 52 | build/Release 53 | 54 | # Dependency directories 55 | node_modules/ 56 | jspm_packages/ 57 | 58 | # TypeScript v1 declaration files 59 | typings/ 60 | 61 | # TypeScript cache 62 | *.tsbuildinfo 63 | 64 | # Optional npm cache directory 65 | .npm 66 | 67 | # Optional eslint cache 68 | .eslintcache 69 | 70 | # Microbundle cache 71 | .rpt2_cache/ 72 | .rts2_cache_cjs/ 73 | .rts2_cache_es/ 74 | .rts2_cache_umd/ 75 | 76 | # Optional REPL history 77 | .node_repl_history 78 | 79 | # Output of 'npm pack' 80 | *.tgz 81 | 82 | # Yarn Integrity file 83 | .yarn-integrity 84 | 85 | # dotenv environment variables file 86 | .env 87 | .env.test 88 | 89 | # parcel-bundler cache (https://parceljs.org/) 90 | .cache 91 | 92 | # Next.js build output 93 | .next 94 | 95 | # Nuxt.js build / generate output 96 | .nuxt 97 | 98 | # Gatsby files 99 | .cache/ 100 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 101 | # https://nextjs.org/blog/next-9-1#public-directory-support 102 | # public 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | 119 | .DS_Store 120 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "samples/mrjsio"] 2 | path = samples/mrjsio 3 | url = git@github.com:Volumetrics-io/mrjs-landing.git 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "semi": true, 4 | "singleQuote": true, 5 | "tabWidth": 4, 6 | "useTabs": false, 7 | "printWidth": 180 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "eslint.probe": [ 5 | "javascript", 6 | "javascriptreact", 7 | "vue" 8 | ], 9 | "editor.formatOnSave": false, 10 | // Runs Prettier, then ESLint 11 | "editor.codeActionsOnSave": [ 12 | "source.formatDocument", 13 | "source.fixAll.eslint" 14 | ], 15 | "vetur.validation.template": false 16 | } 17 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT: -------------------------------------------------------------------------------- 1 | Please read our full contributing guide found on our main site: [here](https://docs.mrjs.io/contribute/#code-of-conduct) 2 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | Please read our full contributing guide found on our main site: [here](https://docs.mrjs.io/contribute/) 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Volumetrics-io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /__tests__/checkSubmoduleTesting.test.js: -------------------------------------------------------------------------------- 1 | // import { exec } from 'child_process'; 2 | 3 | // // Function to promisify exec for easier use with async/await 4 | // const execPromise = (cmd) => 5 | // new Promise((resolve, reject) => { 6 | // exec(cmd, (error, stdout, stderr) => { 7 | // if (error) { 8 | // // Attach stdout and stderr to the error object for better debugging 9 | // error.stdout = stdout; 10 | // error.stderr = stderr; 11 | // reject(error); 12 | // } else { 13 | // resolve({ stdout, stderr, code: 0 }); // Explicitly resolve with code 0 for success 14 | // } 15 | // }); 16 | // }); 17 | 18 | // Check that the mrjsio submodule is up to date 19 | // - note: doing this as a test instead of another github action is that 20 | // it's a simpler process to ask the user to update following the 21 | // test failure considering it's more locally dependent. 22 | test('mrjsio submodule is up to date', async () => { 23 | // Need to comment out this testing file as it causes an improper merge/stash/branch setup when run with 24 | // the rest of the tests automatically. Will bring it back as part of #608 being resolved 25 | console.log('SKIPPING THIS FOR NOW - see pending issue: https://github.com/Volumetrics-io/mrjs/issues/608'); 26 | // try { 27 | // const result = await execPromise('./scripts/check-if-submodule-needs-update.sh ./samples/mrjsio'); 28 | // // If the promise resolves, it means the script exited with code 0 29 | // expect(result.code).toBe(0); 30 | // } catch (err) { 31 | // // If the script exits with a non-zero exit code, it will be caught here 32 | // console.error('Script failed to execute:', err.stderr); 33 | // console.log('!!! mrjsio submodule needs to be updated !!! run: `npm run update-submodules` and it will handle the rest for you :)'); 34 | 35 | // // Fail the test by checking the exit code - since success is 0, checking against 36 | // // 0 is guaranteed to trigger a failure. 37 | // expect(err.code).toBe(0); 38 | // } 39 | }); 40 | -------------------------------------------------------------------------------- /__tests__/examplesTesting.test.js: -------------------------------------------------------------------------------- 1 | import * as puppeteer from 'puppeteer'; 2 | import fs from 'fs/promises'; 3 | 4 | // todo: in future dont hard code this, but the relative links based on filepath dont work 5 | // using a server to host them works best, so just grabbing from github is fine for now. 6 | const fileNames = ['../index', 'anchors', 'audio', 'camera', 'debug', 'embed', 'images', 'models', 'panels', 'skybox', 'video']; 7 | 8 | describe('Test the Examples', () => { 9 | let browser; 10 | let page; 11 | let errors = []; 12 | 13 | beforeAll(async () => { 14 | browser = await puppeteer.launch({ headless: true }); 15 | page = await browser.newPage(); 16 | 17 | page.on('console', msg => { 18 | if (msg.type() === 'error') { 19 | errors.push(msg.text()); 20 | console.error(`Console error: ${msg.text()}`); 21 | } else { 22 | console.log('PAGE LOG:', msg.text()); 23 | } 24 | }); 25 | 26 | // page.on('pageerror', error => { 27 | // errors.push(error.toString()); 28 | // console.error(`Unhandled error: ${error}`); 29 | // }); 30 | }); 31 | 32 | afterAll(async () => { 33 | await browser.close(); 34 | }); 35 | 36 | fileNames.forEach(fileName => { 37 | test(`Page ${fileName} should load with no console errors`, async () => { 38 | errors = []; 39 | 40 | let htmlContent = await fs.readFile(`./dist/examples/${fileName}.html`, 'utf8'); 41 | console.log(`Running test on: ./dist/examples/${fileName}.html`); 42 | 43 | // Adjust script path to load mr.js relatively, index.html is in propert spot already 44 | if (fileName != "../index") { 45 | htmlContent = htmlContent.replace( 46 | ``, 47 | `` 48 | ); 49 | } 50 | 51 | await page.setContent(htmlContent); 52 | 53 | expect(errors).toHaveLength(0); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /__tests__/loadModelsTesting.test.js: -------------------------------------------------------------------------------- 1 | import * as puppeteer from 'puppeteer'; 2 | 3 | // todo: in future dont hard code this, but the relative links based on filepath dont work 4 | // using a server to host them works best, so just grabbing from github is fine for now. 5 | const MODELS_URL = 'https://github.com/Volumetrics-io/mrjs/main/samples/assets/models/' 6 | 7 | describe('loadModel Function', () => { 8 | let browser; 9 | let page; 10 | let errors = []; 11 | 12 | beforeAll(async () => { 13 | browser = await puppeteer.launch({ headless: "new" }); 14 | page = await browser.newPage(); 15 | 16 | // Listen for console events and record any errors 17 | page.on('console', msg => { 18 | if (msg.type() === 'error') { 19 | errors.push(msg.text()); 20 | } 21 | }); 22 | 23 | console.log(`Running LoadModel function tests`); 24 | 25 | // Define your HTML content 26 | const htmlContent = ` 27 | 28 | 29 | 30 | MR.js - TEST PAGE 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | `; 51 | 52 | // Set the content of the page 53 | await page.setContent(htmlContent); 54 | }); 55 | 56 | afterAll(async () => { 57 | await browser.close(); 58 | }); 59 | 60 | test('Page should load with no console errors', async () => { 61 | if (errors.length > 0) { 62 | console.log(`Console Errors:`, errors); 63 | } 64 | expect(errors).toHaveLength(0); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /dist/57cf7fc5ff4d6dfc74e4.module.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/dist/57cf7fc5ff4d6dfc74e4.module.wasm -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | transform: {}, 3 | // Specify the root directory for Jest to run tests from 4 | rootDir: './', 5 | 6 | // Test environment options 7 | testEnvironment: 'node', // Use Node.js environment for testing 8 | }; 9 | -------------------------------------------------------------------------------- /jsdoc.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true 4 | }, 5 | "source": { 6 | "include": ["src"], 7 | "includePattern": ".+\\.js(doc|x)?$", 8 | "excludePattern": "(^|\\/|\\\\)_" 9 | }, 10 | "opts": { 11 | "template": "node_modules/tui-jsdoc-template", 12 | "recurse": true, 13 | "verbose": true, 14 | "encoding": "utf8", 15 | "destination": "./docs" 16 | }, 17 | "templates": { 18 | "cleverLinks": false, 19 | "monospaceLinks": false, 20 | "default": { 21 | "outputSourceFiles": true, 22 | "includeDate": false, 23 | "useLongnameInNav": true 24 | }, 25 | "tabNames": { 26 | "api": "API", 27 | "tutorials": "Tutorials" 28 | }, 29 | "name": "mrjs Docs", 30 | "footerText": "My awesome footer text", 31 | "useCollapsibles": true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mrjs", 3 | "version": "0.6.4", 4 | "type": "module", 5 | "description": "an MR first webXR framework", 6 | "engines": { 7 | "node": ">=20.0.0", 8 | "npm": ">=6.14.4" 9 | }, 10 | "main": "dist/mr.js", 11 | "homepage": "https://mrjs.io", 12 | "scripts": { 13 | "build": "npx webpack --config webpack.config.js", 14 | "update-submodules": "./scripts/update-all-submodules.sh", 15 | "server": "npx webpack serve", 16 | "test-server": "NODE_ENV=testing npx webpack serve", 17 | "test": "NODE_OPTIONS=--experimental-vm-modules npx jest", 18 | "test-serially": "NODE_OPTIONS=--experimental-vm-modules npx jest --runInBand", 19 | "test-randomized": "NODE_OPTIONS=--experimental-vm-modules npx jest --runInBand --random", 20 | "clear-testing-cache": "npx jest --clearCache", 21 | "docs": "./scripts/create-docs.sh", 22 | "prettier-check": "prettier --check \"src/**/*.js\" \"*.js\"", 23 | "prettier-fix": "prettier --write \"src/**/*.js\" \"*.js\"", 24 | "lint-check": "eslint \"src/**/*.js\" \"*.js\" --ignore-pattern \"src/extras/**\" --max-warnings 0", 25 | "lint-fix": "eslint \"src/**/*.js\" \"*.js\" --ignore-pattern \"src/extras/**\" --fix", 26 | "format": "npm run prettier-fix && npm run lint-fix", 27 | "check-format": "npm run prettier-check && npm run lint-check" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/Volumetrics-io/mrjs.git" 32 | }, 33 | "author": "Volumetrics", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/Volumetrics-io/mrjs/issues" 37 | }, 38 | "engines": { 39 | "node": "^18.17.0 || >=20.5.0" 40 | }, 41 | "testMatch": [ 42 | "**/__tests__/**/*.test.mjs" 43 | ], 44 | "testEnvironment": "node", 45 | "devDependencies": { 46 | "@babel/core": "^7.23.9", 47 | "babel-jest": "^29.7.0", 48 | "copy-webpack-plugin": "^12.0.2", 49 | "css-loader": "^7.1.1", 50 | "eslint": "^8.56.0", 51 | "eslint-config-airbnb": "^19.0.4", 52 | "eslint-config-airbnb-base": "^15.0.0", 53 | "eslint-config-prettier": "^9.1.0", 54 | "eslint-plugin-import": "^2.29.1", 55 | "eslint-plugin-jsdoc": "^48.2.4", 56 | "eslint-plugin-jsx-a11y": "^6.8.0", 57 | "eslint-plugin-prettier": "^5.1.3", 58 | "eslint-plugin-react": "^7.33.2", 59 | "eslint-plugin-react-hooks": "^4.6.0", 60 | "eslint-plugin-unused-imports": "^3.0.0", 61 | "esm": "^3.2.25", 62 | "html-webpack-plugin": "^5.6.0", 63 | "install": "^0.13.0", 64 | "jest": "^29.7.0", 65 | "jest-environment-jsdom": "^29.7.0", 66 | "jest-fetch-mock": "^3.0.3", 67 | "jest-puppeteer": "^10.0.1", 68 | "jsdoc": "^4.0.2", 69 | "jsdoc-to-markdown": "^8.0.1", 70 | "json-loader": "^0.5.7", 71 | "mini-css-extract-plugin": "^2.8.0", 72 | "npm": "^10.4.0", 73 | "playwright": "^1.41.2", 74 | "puppeteer": "^22.8.0", 75 | "style-loader": "^4.0.0", 76 | "tui-jsdoc-template": "^1.2.2", 77 | "url": "^0.11.3", 78 | "webpack": "^5.90.1", 79 | "webpack-cli": "^5.1.4", 80 | "webpack-dev-server": "^5.0.4" 81 | }, 82 | "dependencies": { 83 | "@babel/eslint-parser": "^7.23.10", 84 | "@dimforge/rapier3d": "^0.12.0", 85 | "docdash": "^2.0.2", 86 | "jaguarjs-jsdoc": "^1.1.0", 87 | "jsdom": "^24.0.0", 88 | "prettier": "^3.2.4", 89 | "prettier-eslint-cli": "^8.0.1", 90 | "stats.js": "^0.17.0", 91 | "three": "^0.161.0", 92 | "troika-three-text": "^0.48.1" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /samples/examples-assets/audio/rain-loop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/audio/rain-loop.wav -------------------------------------------------------------------------------- /samples/examples-assets/audio/thunder-clap1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/audio/thunder-clap1.mp3 -------------------------------------------------------------------------------- /samples/examples-assets/audio/thunder-clap2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/audio/thunder-clap2.wav -------------------------------------------------------------------------------- /samples/examples-assets/fonts/BricolageGrotesque.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/fonts/BricolageGrotesque.ttf -------------------------------------------------------------------------------- /samples/examples-assets/fonts/BricolageGrotesque.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/fonts/BricolageGrotesque.woff -------------------------------------------------------------------------------- /samples/examples-assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /samples/examples-assets/fonts/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/fonts/Roboto-Regular.woff -------------------------------------------------------------------------------- /samples/examples-assets/js/SpinSystem.js: -------------------------------------------------------------------------------- 1 | class SpinSystem extends MRSystem { 2 | constructor() { 3 | super() 4 | 5 | console.log(this.componentName); 6 | } 7 | 8 | update(deltaTime, frame) { 9 | for(const entity of this.registry){ 10 | let component = entity.components.get("spin") 11 | if (Math.abs(component.speed) < Math.abs(component.maxspeed)) { 12 | entity.components.set("spin", { speed: parseFloat(component.speed) + parseFloat(component.acceleration) }) 13 | } 14 | entity.rotation.z += parseFloat(component.speed); 15 | // entity.object3D.rotation.z += parseFloat(component.speed); 16 | } 17 | } 18 | 19 | attachedComponent(entity) { 20 | entity.components.set("spin", { speed: 0 }) 21 | } 22 | 23 | detachedComponent(entity) { 24 | 25 | } 26 | 27 | rotate = (entity, component) => { 28 | 29 | } 30 | } 31 | 32 | let spinsys = new SpinSystem() 33 | -------------------------------------------------------------------------------- /samples/examples-assets/models/Basecolor_0.001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/Basecolor_0.001.jpg -------------------------------------------------------------------------------- /samples/examples-assets/models/Basecolor_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/Basecolor_0.jpg -------------------------------------------------------------------------------- /samples/examples-assets/models/Mars.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/Mars.glb -------------------------------------------------------------------------------- /samples/examples-assets/models/animation_koifish.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/animation_koifish.glb -------------------------------------------------------------------------------- /samples/examples-assets/models/blue_whale.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/blue_whale.glb -------------------------------------------------------------------------------- /samples/examples-assets/models/dae/SpiderPlant_Base_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/dae/SpiderPlant_Base_color.png -------------------------------------------------------------------------------- /samples/examples-assets/models/dae/attribution.txt: -------------------------------------------------------------------------------- 1 | "Spider Plant" (https://skfb.ly/ovuTv) by sauti is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/). 2 | -------------------------------------------------------------------------------- /samples/examples-assets/models/daffodils_island.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/daffodils_island.glb -------------------------------------------------------------------------------- /samples/examples-assets/models/door1-UI.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/door1-UI.glb -------------------------------------------------------------------------------- /samples/examples-assets/models/door1.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/door1.glb -------------------------------------------------------------------------------- /samples/examples-assets/models/fbx/FourthGreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/fbx/FourthGreen.png -------------------------------------------------------------------------------- /samples/examples-assets/models/fbx/animatedChick.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/fbx/animatedChick.fbx -------------------------------------------------------------------------------- /samples/examples-assets/models/fbx/attribution.txt: -------------------------------------------------------------------------------- 1 | "Animated Chick" (https://skfb.ly/6WLZu) by FourthGreen is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/). 2 | -------------------------------------------------------------------------------- /samples/examples-assets/models/glb/attribution.txt: -------------------------------------------------------------------------------- 1 | "Walkman" (https://skfb.ly/o6PYB) by Tom Seddon is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/) 2 | -------------------------------------------------------------------------------- /samples/examples-assets/models/glb/cassetteTape.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/glb/cassetteTape.glb -------------------------------------------------------------------------------- /samples/examples-assets/models/great_hammerhead_shark.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/great_hammerhead_shark.glb -------------------------------------------------------------------------------- /samples/examples-assets/models/island3.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/island3.glb -------------------------------------------------------------------------------- /samples/examples-assets/models/koifish.mtl: -------------------------------------------------------------------------------- 1 | # Blender 4.0.1 MTL File: 'None' 2 | # www.blender.org 3 | 4 | newmtl SimplygonCastMaterial 5 | Ns 0.000000 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.800000 0.800000 0.800000 8 | Ks 0.000000 0.000000 0.000000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.450000 11 | d 1.000000 12 | illum 1 13 | -------------------------------------------------------------------------------- /samples/examples-assets/models/logo.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/logo.bin -------------------------------------------------------------------------------- /samples/examples-assets/models/logo.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/logo.fbx -------------------------------------------------------------------------------- /samples/examples-assets/models/logo.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/logo.glb -------------------------------------------------------------------------------- /samples/examples-assets/models/logo.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset":{ 3 | "generator":"Khronos glTF Blender I/O v4.0.43", 4 | "version":"2.0" 5 | }, 6 | "scene":0, 7 | "scenes":[ 8 | { 9 | "name":"Scene", 10 | "nodes":[ 11 | 0 12 | ] 13 | } 14 | ], 15 | "nodes":[ 16 | { 17 | "mesh":0, 18 | "name":"logo" 19 | } 20 | ], 21 | "meshes":[ 22 | { 23 | "name":"logo", 24 | "primitives":[ 25 | { 26 | "attributes":{ 27 | "POSITION":0, 28 | "NORMAL":1 29 | }, 30 | "indices":2 31 | } 32 | ] 33 | } 34 | ], 35 | "accessors":[ 36 | { 37 | "bufferView":0, 38 | "componentType":5126, 39 | "count":6518, 40 | "max":[ 41 | 28.86751365661621, 42 | 30.618621826171875, 43 | 25 44 | ], 45 | "min":[ 46 | -21.13202476501465, 47 | -19.381378173828125, 48 | -25 49 | ], 50 | "type":"VEC3" 51 | }, 52 | { 53 | "bufferView":1, 54 | "componentType":5126, 55 | "count":6518, 56 | "type":"VEC3" 57 | }, 58 | { 59 | "bufferView":2, 60 | "componentType":5123, 61 | "count":39096, 62 | "type":"SCALAR" 63 | } 64 | ], 65 | "bufferViews":[ 66 | { 67 | "buffer":0, 68 | "byteLength":78216, 69 | "byteOffset":0, 70 | "target":34962 71 | }, 72 | { 73 | "buffer":0, 74 | "byteLength":78216, 75 | "byteOffset":78216, 76 | "target":34962 77 | }, 78 | { 79 | "buffer":0, 80 | "byteLength":78192, 81 | "byteOffset":156432, 82 | "target":34963 83 | } 84 | ], 85 | "buffers":[ 86 | { 87 | "byteLength":234624, 88 | "uri":"logo.bin" 89 | } 90 | ] 91 | } 92 | -------------------------------------------------------------------------------- /samples/examples-assets/models/logo.ply: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/logo.ply -------------------------------------------------------------------------------- /samples/examples-assets/models/logo.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/logo.stl -------------------------------------------------------------------------------- /samples/examples-assets/models/logo.usdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/logo.usdc -------------------------------------------------------------------------------- /samples/examples-assets/models/logo.usdz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/logo.usdz -------------------------------------------------------------------------------- /samples/examples-assets/models/obj1/attribution.txt: -------------------------------------------------------------------------------- 1 | "Cow - Farm Animal - 3December2022" (https://skfb.ly/oAV6x) by Kyuta is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/). 2 | -------------------------------------------------------------------------------- /samples/examples-assets/models/obj2/attribution.txt: -------------------------------------------------------------------------------- 1 | "Still life, Based on Heather's artwork" (https://skfb.ly/oBuAM) by Omar Faruq Tawsif is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/). 2 | -------------------------------------------------------------------------------- /samples/examples-assets/models/obj2/board n knife Base Color.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/obj2/board n knife Base Color.jpg -------------------------------------------------------------------------------- /samples/examples-assets/models/obj2/food Base Color.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/obj2/food Base Color.jpg -------------------------------------------------------------------------------- /samples/examples-assets/models/obj2/stillLife.mtl: -------------------------------------------------------------------------------- 1 | # Blender 4.0.2 MTL File: 'stillLife.blend' 2 | # www.blender.org 3 | 4 | newmtl board_n_knife 5 | Ns 250.000000 6 | Ka 1.000000 1.000000 1.000000 7 | Ks 0.500000 0.500000 0.500000 8 | Ke 0.000000 0.000000 0.000000 9 | Ni 1.450000 10 | d 1.000000 11 | illum 2 12 | map_Kd board n knife Base Color.jpg 13 | 14 | newmtl food 15 | Ns 250.000000 16 | Ka 1.000000 1.000000 1.000000 17 | Ks 0.500000 0.500000 0.500000 18 | Ke 0.000000 0.000000 0.000000 19 | Ni 1.450000 20 | d 1.000000 21 | illum 2 22 | map_Kd food Base Color.jpg 23 | -------------------------------------------------------------------------------- /samples/examples-assets/models/poof1.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/poof1.glb -------------------------------------------------------------------------------- /samples/examples-assets/models/tiledesert001.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/models/tiledesert001.glb -------------------------------------------------------------------------------- /samples/examples-assets/textures/Water_1_M_Flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/Water_1_M_Flow.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/Water_1_M_Normal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/Water_1_M_Normal.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/Water_2_M_Normal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/Water_2_M_Normal.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/bump.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/bump.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/founder-shot.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/founder-shot.jpeg -------------------------------------------------------------------------------- /samples/examples-assets/textures/headset-keyboard.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/headset-keyboard.jpeg -------------------------------------------------------------------------------- /samples/examples-assets/textures/night_life.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/night_life.jpeg -------------------------------------------------------------------------------- /samples/examples-assets/textures/simple.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/simple.webm -------------------------------------------------------------------------------- /samples/examples-assets/textures/skybox_daytime/nx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/skybox_daytime/nx.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/skybox_daytime/ny.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/skybox_daytime/ny.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/skybox_daytime/nz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/skybox_daytime/nz.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/skybox_daytime/px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/skybox_daytime/px.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/skybox_daytime/py.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/skybox_daytime/py.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/skybox_daytime/pz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/skybox_daytime/pz.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/skybox_hdri.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/skybox_hdri.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/skybox_milkyway.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/skybox_milkyway.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/skybox_starmap_4k.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/skybox_starmap_4k.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/skybox_surreal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/skybox_surreal.jpg -------------------------------------------------------------------------------- /samples/examples-assets/textures/skybox_watercolor01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/examples-assets/textures/skybox_watercolor01.jpg -------------------------------------------------------------------------------- /samples/examples/anchors-style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | src: url('/examples-assets/fonts/Roboto-Regular.ttf') format('truetype'); 4 | font-weight: 100 900; 5 | } 6 | 7 | @font-face { 8 | font-family: 'Bricolage'; 9 | src: url('/examples-assets/fonts/BricolageGrotesque.ttf') format('truetype'); 10 | font-weight: 100 900; 11 | } 12 | 13 | 14 | :root { 15 | --font-title: "Bricolage"; 16 | /* --font-body: "Onest", system-ui, sans-serif; */ 17 | /* PADDINGS & MARGINS */ 18 | --space: 4rem; 19 | --outer: 1.5rem; 20 | --inter: 1rem; 21 | --inner: 0.5rem; 22 | --inlet: 0.25rem; 23 | /* CONSTRAINTS */ 24 | --hwdth: 56rem; 25 | --mwdth: 68rem; 26 | /* RADIUS */ 27 | --border-radius-large: 2rem; 28 | /* SCROLL */ 29 | --scroll: 0; 30 | --position: 0; 31 | /* NEUTRAL COLORS */ 32 | --neutral-hue: 40; 33 | --color-title: hsl(var(--neutral-hue), 20%, 20%); 34 | --color-text: hsl(var(--neutral-hue), 15%, 10%); 35 | --color-text-light: hsl(var(--neutral-hue), 20%, 42%); 36 | --color-paper: hsl(var(--neutral-hue), 80%, 94%); 37 | --color-paper-light: hsl(var(--neutral-hue), 75%, 92%); 38 | --color-paper-card: white; 39 | /* BUTTONS */ 40 | /* --rythm-hue: 310; */ 41 | --rythm-hue: 300; 42 | /* --rythm-hue: 40; */ 43 | --color-button: hsl(var(--rythm-hue), 15%, 96%); 44 | --color-button-hover: hsl(var(--rythm-hue), 100%, 90%); 45 | --color-button-active: hsl(var(--rythm-hue), 100%, 85%); 46 | --color-button-text: hsl(var(--rythm-hue), 15%, 15%); 47 | --color-button-ring: hsl(var(--rythm-hue), 100%, 70%); 48 | /* CTA */ 49 | /* --hue: 210; */ 50 | --hue: 210; 51 | --color-cta: hsl(var(--hue), 70%, 40%); 52 | --color-cta-hover: hsl(var(--hue), 70%, 55%); 53 | --color-cta-active: hsl(var(--hue), 70%, 60%); 54 | --color-cta-text: hsl(var(--hue), 10%, 100%); 55 | --color-cta-focus-ring: hsl(var(--hue), 100%, 100%); 56 | /* OPTIONS */ 57 | --option-hue: 40; 58 | --color-option: hsl(var(--option-hue), 50%, 83%); 59 | --color-option-hover: hsl(var(--option-hue), 50%, 86%); 60 | --color-option-active: hsl(var(--option-hue), 50%, 88%); 61 | --color-option-text: var(--color-text); 62 | --color-option-focus-ring: hsl(var(--option-hue), 50%, 50%); 63 | /* OPTIONS SELECTED */ 64 | --color-option-selected: hsl(var(--option-hue), 100%, 100%); 65 | --color-option-selected-text: hsl(var(--hue), 80%, 30%); 66 | /* FOCUS */ 67 | --color-focus-outline: hsl(var(--hue), 100%, 50%); 68 | /* BORDERS */ 69 | --color-border-light: hsl(var(--rythm-hue), 15%, 92%); 70 | --color-border-static: hsla(var(--rythm-hue), 100%, 15%, 15%); 71 | --color-border-field-hover: hsl(var(--rythm-hue), 80%, 80%); 72 | --color-border-field-active: hsl(var(--rythm-hue), 100%, 70%); 73 | /* SHADOWS */ 74 | --button-shadow: 0 0.25rem 1rem -0.5rem hsla(30, 15%, 15%, 20%); 75 | --main-shadow: 0 0 1rem -0.5rem hsla(30, 50%, 25%, 50%); 76 | } 77 | 78 | mr-text { 79 | font-family: 'Roboto'; 80 | line-height: 100%; 81 | font-size: 16px; 82 | } 83 | 84 | .mrjs { 85 | font-family: var(--font-title); 86 | font-size: 4vw; 87 | line-height: 100%; 88 | color: rgb(255, 255, 255); 89 | grid-column: 1; 90 | } 91 | 92 | .company { 93 | font-family: var(--font-title); 94 | font-size: 4vw; 95 | line-height: 100%; 96 | color: rgb(255, 255, 255); 97 | align-self: center; 98 | grid-column: 4; 99 | } 100 | 101 | .logo { 102 | grid-column: 3; 103 | z-index: 25; 104 | } 105 | 106 | #logo-model{ 107 | scale: 0.0005; 108 | } 109 | 110 | 111 | .layout { 112 | background-color: var(--color-paper); 113 | padding-top: 6vw; 114 | display: grid; 115 | grid-template-columns: 1fr 2fr 1fr; 116 | gap: 1vw; 117 | grid-auto-rows: auto; 118 | } 119 | 120 | .col-1{ 121 | grid-column: 1; 122 | } 123 | 124 | .col-2{ 125 | grid-column: 2; 126 | } 127 | 128 | .col-3{ 129 | grid-column: 3; 130 | } 131 | 132 | #navbar { 133 | background-color: #141414; 134 | border-radius: 1%; 135 | position:fixed; 136 | top: 0; 137 | left: 0; 138 | width: 100%; 139 | z-index: 4; 140 | display: grid; 141 | grid-template-columns: 1fr 2fr 0.5fr 0.5fr; 142 | padding: 10px; 143 | } 144 | 145 | .title { 146 | margin: 0 auto; 147 | font-family: var(--font-title); 148 | font-weight: 500; 149 | font-size: 8vw; 150 | line-height: 100%; 151 | color: rgba(24, 24, 24, 0.75); 152 | font-weight: bold; 153 | align-self: center; 154 | 155 | } 156 | 157 | .title-2 { 158 | font-family: var(--font-title); 159 | font-size: 5vw; 160 | margin-bottom: 1vh; 161 | line-height: 100%; 162 | color: rgba(24, 24, 24, 0.75); 163 | align-self: center; 164 | margin: 0 auto; 165 | } 166 | .subtitle { 167 | font-family: var(--font-title); 168 | font-size: 3vw; 169 | margin-bottom: 1vh; 170 | line-height: 100%; 171 | color: rgba(24, 24, 24, 0.75); 172 | } 173 | 174 | .label { 175 | font-family: var(--font-title); 176 | font-size: 2vw; 177 | line-height: 100%; 178 | color: rgba(24, 24, 24, 0.75); 179 | } 180 | 181 | .label-2 { 182 | font-family: var(--font-title); 183 | font-size: 1.5vw; 184 | line-height: 100%; 185 | color: rgba(24, 24, 24, 0.75); 186 | } 187 | 188 | .button { 189 | padding: 10px; 190 | margin: 10px; 191 | width: fit-content; 192 | } 193 | -------------------------------------------------------------------------------- /samples/examples/audio-style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | src: url('/examples-assets/fonts/Roboto-Regular.ttf') format('truetype'); 4 | font-weight: 100 900; 5 | } 6 | 7 | @font-face { 8 | font-family: 'Bricolage'; 9 | src: url('/examples-assets/fonts/BricolageGrotesque.ttf') format('truetype'); 10 | font-weight: 100 900; 11 | } 12 | 13 | 14 | :root { 15 | --font-title: "Bricolage"; 16 | /* --font-body: "Onest", system-ui, sans-serif; */ 17 | /* PADDINGS & MARGINS */ 18 | --space: 4rem; 19 | --outer: 1.5rem; 20 | --inter: 1rem; 21 | --inner: 0.5rem; 22 | --inlet: 0.25rem; 23 | /* CONSTRAINTS */ 24 | --hwdth: 56rem; 25 | --mwdth: 68rem; 26 | /* RADIUS */ 27 | --border-radius-large: 2rem; 28 | /* SCROLL */ 29 | --scroll: 0; 30 | --position: 0; 31 | /* NEUTRAL COLORS */ 32 | --neutral-hue: 40; 33 | --color-title: hsl(var(--neutral-hue), 20%, 20%); 34 | --color-text: hsl(var(--neutral-hue), 15%, 10%); 35 | --color-text-light: hsl(var(--neutral-hue), 20%, 42%); 36 | --color-paper: hsl(var(--neutral-hue), 80%, 94%); 37 | --color-paper-light: hsl(var(--neutral-hue), 75%, 92%); 38 | --color-paper-card: white; 39 | /* BUTTONS */ 40 | /* --rythm-hue: 310; */ 41 | --rythm-hue: 300; 42 | /* --rythm-hue: 40; */ 43 | --color-button: hsl(var(--rythm-hue), 15%, 96%); 44 | --color-button-hover: hsl(var(--rythm-hue), 100%, 90%); 45 | --color-button-active: hsl(var(--rythm-hue), 100%, 85%); 46 | --color-button-text: hsl(var(--rythm-hue), 15%, 15%); 47 | --color-button-ring: hsl(var(--rythm-hue), 100%, 70%); 48 | /* CTA */ 49 | /* --hue: 210; */ 50 | --hue: 210; 51 | --color-cta: hsl(var(--hue), 70%, 40%); 52 | --color-cta-hover: hsl(var(--hue), 70%, 55%); 53 | --color-cta-active: hsl(var(--hue), 70%, 60%); 54 | --color-cta-text: hsl(var(--hue), 10%, 100%); 55 | --color-cta-focus-ring: hsl(var(--hue), 100%, 100%); 56 | /* OPTIONS */ 57 | --option-hue: 40; 58 | --color-option: hsl(var(--option-hue), 50%, 83%); 59 | --color-option-hover: hsl(var(--option-hue), 50%, 86%); 60 | --color-option-active: hsl(var(--option-hue), 50%, 88%); 61 | --color-option-text: var(--color-text); 62 | --color-option-focus-ring: hsl(var(--option-hue), 50%, 50%); 63 | /* OPTIONS SELECTED */ 64 | --color-option-selected: hsl(var(--option-hue), 100%, 100%); 65 | --color-option-selected-text: hsl(var(--hue), 80%, 30%); 66 | /* FOCUS */ 67 | --color-focus-outline: hsl(var(--hue), 100%, 50%); 68 | /* BORDERS */ 69 | --color-border-light: hsl(var(--rythm-hue), 15%, 92%); 70 | --color-border-static: hsla(var(--rythm-hue), 100%, 15%, 15%); 71 | --color-border-field-hover: hsl(var(--rythm-hue), 80%, 80%); 72 | --color-border-field-active: hsl(var(--rythm-hue), 100%, 70%); 73 | /* SHADOWS */ 74 | --button-shadow: 0 0.25rem 1rem -0.5rem hsla(30, 15%, 15%, 20%); 75 | --main-shadow: 0 0 1rem -0.5rem hsla(30, 50%, 25%, 50%); 76 | } 77 | 78 | mr-text { 79 | font-family: 'Roboto'; 80 | line-height: 100%; 81 | font-size: 16px; 82 | } 83 | 84 | .mrjs { 85 | font-family: var(--font-title); 86 | font-size: 4vw; 87 | line-height: 100%; 88 | color: rgb(255, 255, 255); 89 | grid-column: 1; 90 | } 91 | 92 | .company { 93 | font-family: var(--font-title); 94 | font-size: 4vw; 95 | line-height: 100%; 96 | color: rgb(255, 255, 255); 97 | align-self: center; 98 | grid-column: 4; 99 | } 100 | 101 | .logo { 102 | grid-column: 3; 103 | z-index: 25; 104 | } 105 | 106 | #logo-model{ 107 | scale: 0.0005; 108 | } 109 | 110 | 111 | .layout { 112 | background-color: var(--color-paper); 113 | padding-top: 6vw; 114 | display: grid; 115 | grid-template-columns: 1fr 2fr 1fr; 116 | gap: 1vw; 117 | grid-auto-rows: auto; 118 | } 119 | 120 | .col-1{ 121 | grid-column: 1; 122 | } 123 | 124 | .col-2{ 125 | grid-column: 2; 126 | } 127 | 128 | .col-3{ 129 | grid-column: 3; 130 | } 131 | 132 | #navbar { 133 | background-color: #141414; 134 | border-radius: 1%; 135 | position:fixed; 136 | top: 0; 137 | left: 0; 138 | width: 100%; 139 | z-index: 4; 140 | display: grid; 141 | grid-template-columns: 1fr 2fr 0.5fr 0.5fr; 142 | padding: 10px; 143 | } 144 | 145 | .title { 146 | margin: 0 auto; 147 | font-family: var(--font-title); 148 | font-weight: 500; 149 | font-size: 8vw; 150 | line-height: 100%; 151 | color: rgba(24, 24, 24, 0.75); 152 | font-weight: bold; 153 | align-self: center; 154 | 155 | } 156 | 157 | .title-2 { 158 | font-family: var(--font-title); 159 | font-size: 5vw; 160 | margin-bottom: 1vh; 161 | line-height: 100%; 162 | color: rgba(24, 24, 24, 0.75); 163 | align-self: center; 164 | margin: 0 auto; 165 | } 166 | .subtitle { 167 | font-family: var(--font-title); 168 | font-size: 3vw; 169 | margin-bottom: 1vh; 170 | line-height: 100%; 171 | color: rgba(24, 24, 24, 0.75); 172 | } 173 | 174 | .label { 175 | font-family: var(--font-title); 176 | font-size: 2vw; 177 | line-height: 100%; 178 | color: rgba(24, 24, 24, 0.75); 179 | } 180 | 181 | .label-2 { 182 | font-family: var(--font-title); 183 | font-size: 1.5vw; 184 | line-height: 100%; 185 | color: rgba(24, 24, 24, 0.75); 186 | } 187 | 188 | .button { 189 | padding: 10px; 190 | margin: 0 10px; 191 | width: fit-content; 192 | } 193 | -------------------------------------------------------------------------------- /samples/examples/camera.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mr.js - camera 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | mrjs 17 | 18 | 26 | 27 | volumetrics 28 | 29 | 30 | 31 | Camera 32 | 33 | 34 | 35 | The camera can be setup with a few different modifications based on user's purpose. 36 | On `mr-app` a camera is setup at the default starting position. A user can note the startingPositon as well instead. 37 | If using orbital controls, a user can also denote the target position of those controls. 38 | 39 | 40 | 41 | 46 | 47 | 48 | 49 | 50 | This page is its own example and is currently running with the following setup: 51 | 52 | 53 | `data-orbital="mode:true; targetPos:0.5 0.25 0;" data-camera="mode: perspective; startPos:1 1 1;"` 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | -------------------------------------------------------------------------------- /samples/examples/debug-style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | src: url('/examples-assets/fonts/Roboto-Regular.ttf') format('truetype'); 4 | font-weight: 100 900; 5 | } 6 | 7 | @font-face { 8 | font-family: 'Bricolage'; 9 | src: url('/examples-assets/fonts/BricolageGrotesque.ttf') format('truetype'); 10 | font-weight: 100 900; 11 | } 12 | 13 | .diptych { 14 | display: grid; 15 | width: 100%; 16 | grid-template-columns: 1fr 1fr; 17 | /* grid-gap: 10px; */ 18 | justify-items: center; 19 | align-items: center; 20 | } 21 | 22 | 23 | :root { 24 | --font-title: "Bricolage"; 25 | /* --font-body: "Onest", system-ui, sans-serif; */ 26 | /* PADDINGS & MARGINS */ 27 | --space: 4rem; 28 | --outer: 1.5rem; 29 | --inter: 1rem; 30 | --inner: 0.5rem; 31 | --inlet: 0.25rem; 32 | /* CONSTRAINTS */ 33 | --hwdth: 56rem; 34 | --mwdth: 68rem; 35 | /* RADIUS */ 36 | --border-radius-large: 2rem; 37 | /* SCROLL */ 38 | --scroll: 0; 39 | --position: 0; 40 | /* NEUTRAL COLORS */ 41 | --neutral-hue: 40; 42 | --color-title: hsl(var(--neutral-hue), 20%, 20%); 43 | --color-text: hsl(var(--neutral-hue), 15%, 10%); 44 | --color-text-light: hsl(var(--neutral-hue), 20%, 42%); 45 | --color-paper: hsl(var(--neutral-hue), 80%, 94%); 46 | --color-paper-light: hsl(var(--neutral-hue), 75%, 92%); 47 | --color-paper-card: white; 48 | /* BUTTONS */ 49 | /* --rythm-hue: 310; */ 50 | --rythm-hue: 300; 51 | /* --rythm-hue: 40; */ 52 | --color-button: hsl(var(--rythm-hue), 15%, 96%); 53 | --color-button-hover: hsl(var(--rythm-hue), 100%, 90%); 54 | --color-button-active: hsl(var(--rythm-hue), 100%, 85%); 55 | --color-button-text: hsl(var(--rythm-hue), 15%, 15%); 56 | --color-button-ring: hsl(var(--rythm-hue), 100%, 70%); 57 | /* CTA */ 58 | /* --hue: 210; */ 59 | --hue: 210; 60 | --color-cta: hsl(var(--hue), 70%, 40%); 61 | --color-cta-hover: hsl(var(--hue), 70%, 55%); 62 | --color-cta-active: hsl(var(--hue), 70%, 60%); 63 | --color-cta-text: hsl(var(--hue), 10%, 100%); 64 | --color-cta-focus-ring: hsl(var(--hue), 100%, 100%); 65 | /* OPTIONS */ 66 | --option-hue: 40; 67 | --color-option: hsl(var(--option-hue), 50%, 83%); 68 | --color-option-hover: hsl(var(--option-hue), 50%, 86%); 69 | --color-option-active: hsl(var(--option-hue), 50%, 88%); 70 | --color-option-text: var(--color-text); 71 | --color-option-focus-ring: hsl(var(--option-hue), 50%, 50%); 72 | /* OPTIONS SELECTED */ 73 | --color-option-selected: hsl(var(--option-hue), 100%, 100%); 74 | --color-option-selected-text: hsl(var(--hue), 80%, 30%); 75 | /* FOCUS */ 76 | --color-focus-outline: hsl(var(--hue), 100%, 50%); 77 | /* BORDERS */ 78 | --color-border-light: hsl(var(--rythm-hue), 15%, 92%); 79 | --color-border-static: hsla(var(--rythm-hue), 100%, 15%, 15%); 80 | --color-border-field-hover: hsl(var(--rythm-hue), 80%, 80%); 81 | --color-border-field-active: hsl(var(--rythm-hue), 100%, 70%); 82 | /* SHADOWS */ 83 | --button-shadow: 0 0.25rem 1rem -0.5rem hsla(30, 15%, 15%, 20%); 84 | --main-shadow: 0 0 1rem -0.5rem hsla(30, 50%, 25%, 50%); 85 | } 86 | 87 | mr-text, mr-stats { 88 | font-family: 'Roboto'; 89 | line-height: 100%; 90 | font-size: 16px; 91 | } 92 | 93 | .mrjs { 94 | font-family: var(--font-title); 95 | font-size: 4vw; 96 | line-height: 100%; 97 | color: rgb(255, 255, 255); 98 | grid-column: 1; 99 | } 100 | 101 | .company { 102 | font-family: var(--font-title); 103 | font-size: 4vw; 104 | line-height: 100%; 105 | color: rgb(255, 255, 255); 106 | align-self: center; 107 | grid-column: 4; 108 | } 109 | 110 | .logo { 111 | grid-column: 3; 112 | z-index: 25; 113 | } 114 | 115 | .links { 116 | display:grid; 117 | grid-template-columns: 1fr 1fr; 118 | gap: 1vw; 119 | margin-bottom: 10px; 120 | } 121 | 122 | #logo-model{ 123 | scale: 0.0005; 124 | } 125 | 126 | 127 | .layout { 128 | background-color: var(--color-paper); 129 | padding-top: 6vw; 130 | display: grid; 131 | grid-template-columns: 1fr 2fr 1fr; 132 | gap: 1vw; 133 | grid-auto-rows: auto; 134 | } 135 | 136 | .col-1{ 137 | grid-column: 1; 138 | } 139 | 140 | .col-2{ 141 | grid-column: 2; 142 | } 143 | 144 | .col-3{ 145 | grid-column: 3; 146 | } 147 | 148 | #navbar { 149 | background-color: #141414; 150 | border-radius: 1%; 151 | position:fixed; 152 | top: 0; 153 | left: 0; 154 | width: 100%; 155 | z-index: 4; 156 | display: grid; 157 | grid-template-columns: 1fr 2fr 0.5fr 0.5fr; 158 | padding: 10px; 159 | } 160 | 161 | .title { 162 | margin: 0 auto; 163 | font-family: var(--font-title); 164 | font-weight: 500; 165 | font-size: 8vw; 166 | line-height: 100%; 167 | color: rgba(24, 24, 24, 0.75); 168 | font-weight: bold; 169 | align-self: center; 170 | 171 | } 172 | 173 | .title-2 { 174 | font-family: var(--font-title); 175 | font-size: 5vw; 176 | margin-bottom: 1vh; 177 | line-height: 100%; 178 | color: rgba(24, 24, 24, 0.75); 179 | align-self: center; 180 | margin: 0 auto; 181 | } 182 | .subtitle { 183 | font-family: var(--font-title); 184 | font-size: 3vw; 185 | margin-bottom: 1vh; 186 | line-height: 100%; 187 | color: rgba(24, 24, 24, 0.75); 188 | } 189 | 190 | .label { 191 | font-family: var(--font-title); 192 | font-size: 2vw; 193 | line-height: 100%; 194 | color: rgba(24, 24, 24, 0.75); 195 | } 196 | 197 | .label-2 { 198 | font-family: var(--font-title); 199 | font-size: 1.5vw; 200 | line-height: 100%; 201 | color: rgba(24, 24, 24, 0.75); 202 | } 203 | 204 | .button { 205 | padding: 10px; 206 | margin: 10px; 207 | width: fit-content; 208 | } 209 | -------------------------------------------------------------------------------- /samples/examples/debug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mr.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | mrjs 17 | 18 | 26 | 27 | volumetrics 28 | 29 | 30 | 31 | 32 | Debugging 33 | 34 | 35 | 36 | Debug flags can enable the use of certain features for physics, screencapture, and otherwise. 37 | 38 | 39 | 40 | Current flags enabled for this page are `debug="true"`, `stats=true`, and `preserve-drawing-buffer="true"`. 41 | 42 | 43 | 44 | Examples showing the features 45 | 46 | 47 | 48 | 49 | Using the `stats=true` flag on mr-app, creates the blue stats icon in the top left. 50 | This works great on desktop; however, in headset it acts as a huge performance bottleneck. 51 | 52 | To the right, there's the demo of the mr-stats tag. This is just a pure text representation 53 | of the current fps and works well in headset and otherwise. 54 | 55 | 56 | 57 | 58 | 59 | 60 | Model 61 | 62 | 66 | 67 | 68 | 69 | 70 | Image 71 | 72 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 83 | 84 | -------------------------------------------------------------------------------- /samples/examples/embed-style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | src: url('/examples-assets/fonts/Roboto-Regular.ttf') format('truetype'); 4 | font-weight: 100 900; 5 | } 6 | 7 | 8 | :root { 9 | --font-title: "Roboto"; 10 | /* --font-body: "Onest", system-ui, sans-serif; */ 11 | /* PADDINGS & MARGINS */ 12 | --space: 4rem; 13 | --outer: 1.5rem; 14 | --inter: 1rem; 15 | --inner: 0.5rem; 16 | --inlet: 0.25rem; 17 | /* CONSTRAINTS */ 18 | --hwdth: 56rem; 19 | --mwdth: 68rem; 20 | /* RADIUS */ 21 | --border-radius-large: 2rem; 22 | /* SCROLL */ 23 | --scroll: 0; 24 | --position: 0; 25 | /* NEUTRAL COLORS */ 26 | --neutral-hue: 40; 27 | --color-title: hsl(var(--neutral-hue), 20%, 20%); 28 | --color-text: hsl(var(--neutral-hue), 15%, 10%); 29 | --color-text-light: hsl(var(--neutral-hue), 20%, 42%); 30 | --color-paper: hsl(var(--neutral-hue), 80%, 94%); 31 | --color-paper-light: hsl(var(--neutral-hue), 75%, 92%); 32 | --color-paper-card: white; 33 | /* BUTTONS */ 34 | /* --rythm-hue: 310; */ 35 | --rythm-hue: 300; 36 | /* --rythm-hue: 40; */ 37 | --color-button: hsl(var(--rythm-hue), 15%, 96%); 38 | --color-button-hover: hsl(var(--rythm-hue), 100%, 90%); 39 | --color-button-active: hsl(var(--rythm-hue), 100%, 85%); 40 | --color-button-text: hsl(var(--rythm-hue), 15%, 15%); 41 | --color-button-ring: hsl(var(--rythm-hue), 100%, 70%); 42 | /* CTA */ 43 | /* --hue: 210; */ 44 | --hue: 210; 45 | --color-cta: hsl(var(--hue), 70%, 40%); 46 | --color-cta-hover: hsl(var(--hue), 70%, 55%); 47 | --color-cta-active: hsl(var(--hue), 70%, 60%); 48 | --color-cta-text: hsl(var(--hue), 10%, 100%); 49 | --color-cta-focus-ring: hsl(var(--hue), 100%, 100%); 50 | /* OPTIONS */ 51 | --option-hue: 40; 52 | --color-option: hsl(var(--option-hue), 50%, 83%); 53 | --color-option-hover: hsl(var(--option-hue), 50%, 86%); 54 | --color-option-active: hsl(var(--option-hue), 50%, 88%); 55 | --color-option-text: var(--color-text); 56 | --color-option-focus-ring: hsl(var(--option-hue), 50%, 50%); 57 | /* OPTIONS SELECTED */ 58 | --color-option-selected: hsl(var(--option-hue), 100%, 100%); 59 | --color-option-selected-text: hsl(var(--hue), 80%, 30%); 60 | /* FOCUS */ 61 | --color-focus-outline: hsl(var(--hue), 100%, 50%); 62 | /* BORDERS */ 63 | --color-border-light: hsl(var(--rythm-hue), 15%, 92%); 64 | --color-border-static: hsla(var(--rythm-hue), 100%, 15%, 15%); 65 | --color-border-field-hover: hsl(var(--rythm-hue), 80%, 80%); 66 | --color-border-field-active: hsl(var(--rythm-hue), 100%, 70%); 67 | /* SHADOWS */ 68 | --button-shadow: 0 0.25rem 1rem -0.5rem hsla(30, 15%, 15%, 20%); 69 | --main-shadow: 0 0 1rem -0.5rem hsla(30, 50%, 25%, 50%); 70 | } 71 | 72 | /* editor styling */ 73 | 74 | .left-side{ 75 | height: 100%; 76 | width: 100%; 77 | border: medium dashed green; 78 | position: relative; 79 | } 80 | 81 | .left-side * { 82 | position: absolute; 83 | top: 50%; 84 | left: 50%; 85 | transform: translate(-50%, -50%); 86 | } 87 | 88 | .dev-container { 89 | display: grid; 90 | grid-template-columns: 1fr 1fr; 91 | gap: 1vw; 92 | grid-auto-rows: auto; 93 | } 94 | 95 | mr-app { 96 | grid-column: 2; 97 | height: 100vh; 98 | width: 100%; 99 | overflow: scroll; 100 | } 101 | 102 | mr-app > canvas { 103 | overflow: scroll; 104 | } 105 | 106 | /* mr.js app styling */ 107 | mr-text { 108 | font-family: 'Roboto'; 109 | line-height: 100%; 110 | font-size: 16px; 111 | } 112 | 113 | .layout { 114 | background-color: var(--color-paper); 115 | padding-top: 6vw; 116 | display: grid; 117 | width: 100%; 118 | grid-template-columns: 1fr 2fr 1fr; 119 | gap: 1vw; 120 | grid-auto-rows: auto; 121 | } 122 | 123 | .col-1{ 124 | grid-column: 1; 125 | } 126 | 127 | .col-2{ 128 | grid-column: 2; 129 | } 130 | 131 | .col-3{ 132 | grid-column: 3; 133 | } 134 | 135 | #navbar { 136 | background-color: #141414; 137 | border-radius: 1%; 138 | position:fixed; 139 | top: 0; 140 | left: 0; 141 | width: 100%; 142 | z-index: 4; 143 | display: grid; 144 | grid-template-columns: 1fr 2fr 0.5fr 0.5fr; 145 | padding: 10px; 146 | } 147 | 148 | .title { 149 | margin: 0 auto; 150 | font-family: var(--font-title); 151 | font-weight: 500; 152 | font-size: 6vw; 153 | line-height: 100%; 154 | color: rgba(24, 24, 24, 0.75); 155 | font-weight: bold; 156 | align-self: center; 157 | 158 | } 159 | 160 | .title-2 { 161 | font-family: var(--font-title); 162 | font-size: 5vw; 163 | margin-bottom: 1vh; 164 | line-height: 100%; 165 | color: rgba(24, 24, 24, 0.75); 166 | align-self: center; 167 | margin: 0 auto; 168 | } 169 | .subtitle { 170 | font-family: var(--font-title); 171 | font-size: 3vw; 172 | margin-bottom: 1vh; 173 | line-height: 100%; 174 | color: rgba(24, 24, 24, 0.75); 175 | } 176 | 177 | .label { 178 | font-family: var(--font-title); 179 | font-size: 2vw; 180 | line-height: 100%; 181 | color: rgba(24, 24, 24, 0.75); 182 | } 183 | 184 | .label-2 { 185 | font-family: var(--font-title); 186 | font-size: 1.5vw; 187 | line-height: 100%; 188 | color: rgba(24, 24, 24, 0.75); 189 | } 190 | 191 | .button { 192 | padding: 10px; 193 | margin: 0 10px; 194 | width: fit-content; 195 | } 196 | -------------------------------------------------------------------------------- /samples/examples/embed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mrjs - embed 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
Some plane old html
15 | 16 | 17 | 18 | 19 | 20 | hello world! 21 | 22 | 23 | 24 | 25 | Building for Mixed Reality, in Mixed Reality. 26 | 27 | 28 | Ex veniam sunt laboris magna commodo minim eiusmod consequat dolore velit ipsum dolore. Sit nisi id tempor occaecat aute commodo nulla fugiat eu ex sit anim duis. Non eiusmod dolore exercitation adipisicing laboris elit commodo enim proident et. 29 | 30 | Eiusmod tempor veniam et nulla eiusmod ex aute excepteur enim. Velit voluptate proident fugiat aute ea. Qui proident ad consequat enim anim nulla. Est aliquip sint nostrud elit pariatur ipsum dolor magna est eiusmod Lorem pariatur labore. Adipisicing commodo occaecat ea ut nulla magna irure officia nostrud aliqua officia culpa ex pariatur. Aliquip consequat ad proident elit laboris et aliqua nisi est amet tempor. Dolor esse labore laboris reprehenderit id sit. 31 | 32 | Anim ipsum sit dolore id excepteur ex aliqua aute culpa duis adipisicing. Anim magna minim do nisi anim amet pariatur. Nulla ad ullamco labore culpa quis. Incididunt pariatur laboris do duis irure. 33 | 34 | Pariatur excepteur et proident nisi. Id exercitation qui pariatur aliquip eiusmod. Nulla pariatur ad excepteur aliqua dolore sint laboris sit laborum adipisicing voluptate. 35 | 36 | Sit aliqua eu ullamco culpa labore nostrud sit. Ullamco sit cillum excepteur officia irure laboris occaecat. Esse anim ut voluptate excepteur excepteur nostrud laboris. 37 | 38 | Voluptate amet exercitation in consequat adipisicing eu in ea nulla eu occaecat excepteur ea irure. Nisi enim mollit do proident ex. In exercitation nostrud anim ad nulla dolor aute Lorem ipsum nostrud adipisicing pariatur. Deserunt deserunt amet anim cillum. Ex nostrud eu aute eu amet dolor. Ex aute in quis qui irure ut pariatur nisi in. Proident id fugiat anim excepteur cupidatat duis cupidatat adipisicing reprehenderit. 39 | 40 | Sint eiusmod laboris eu voluptate ullamco ea fugiat exercitation id ad magna commodo. Irure proident et qui ullamco aliquip aute dolor nostrud laborum excepteur sint aute ea. Sit consectetur qui Lorem esse magna voluptate irure aliqua nostrud adipisicing eu irure cupidatat sunt. Officia dolore sint laboris Lorem enim labore mollit reprehenderit laboris amet. Aute eiusmod fugiat labore veniam cupidatat dolor ipsum. 41 | 42 | Sit aliquip excepteur laboris ullamco minim sint sit dolor cupidatat culpa consequat quis sit tempor. Culpa voluptate eiusmod et eiusmod. Lorem deserunt ullamco laborum ut. Cillum aute dolore mollit sint amet. Qui ea deserunt consectetur eiusmod. Ullamco qui irure nostrud ea est do. Pariatur laboris labore in irure ea voluptate quis. 43 | 44 | Nostrud deserunt ad officia ut irure cupidatat pariatur ea fugiat. Cillum nisi ullamco tempor consequat laborum quis pariatur cillum enim in elit veniam. Cupidatat laborum do cupidatat irure occaecat sunt nostrud. Adipisicing cillum non adipisicing enim officia excepteur ullamco fugiat. 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | 56 | 57 | 58 | 61 | 62 | -------------------------------------------------------------------------------- /samples/examples/images.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mr.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | volumetrics 17 | 18 | 24 | 25 | 26 | spin 27 | 28 | 29 | 30 | 31 | mr.js 32 | 33 | 34 | 35 | 36 | 37 | Fill 38 | 39 | 40 | 41 | 42 | 43 | Contain 44 | 45 | 46 | 47 | 48 | 49 | Scale-down 50 | 51 | 52 | 53 | 54 | 55 | Cover 56 | 57 | 58 | 59 | 60 | 61 | None (original scaling) 62 | 63 | 64 | 65 | 66 | 67 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 96 | 97 | -------------------------------------------------------------------------------- /samples/examples/skybox-style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | src: url('/examples-assets/fonts/Roboto-Regular.ttf') format('truetype'); 4 | font-weight: 100 900; 5 | } 6 | 7 | @font-face { 8 | font-family: 'Bricolage'; 9 | src: url('/examples-assets/fonts/BricolageGrotesque.ttf') format('truetype'); 10 | font-weight: 100 900; 11 | } 12 | 13 | 14 | :root { 15 | --font-title: "Bricolage"; 16 | /* --font-body: "Onest", system-ui, sans-serif; */ 17 | /* PADDINGS & MARGINS */ 18 | --space: 4rem; 19 | --outer: 1.5rem; 20 | --inter: 1rem; 21 | --inner: 0.5rem; 22 | --inlet: 0.25rem; 23 | /* CONSTRAINTS */ 24 | --hwdth: 56rem; 25 | --mwdth: 68rem; 26 | /* RADIUS */ 27 | --border-radius-large: 2rem; 28 | /* SCROLL */ 29 | --scroll: 0; 30 | --position: 0; 31 | /* NEUTRAL COLORS */ 32 | --neutral-hue: 40; 33 | --color-title: hsl(var(--neutral-hue), 20%, 20%); 34 | --color-text: hsl(var(--neutral-hue), 15%, 10%); 35 | --color-text-light: hsl(var(--neutral-hue), 20%, 42%); 36 | --color-paper: hsl(var(--neutral-hue), 80%, 94%); 37 | --color-paper-light: hsl(var(--neutral-hue), 75%, 92%); 38 | --color-paper-card: white; 39 | /* BUTTONS */ 40 | /* --rythm-hue: 310; */ 41 | --rythm-hue: 300; 42 | /* --rythm-hue: 40; */ 43 | --color-button: hsl(var(--rythm-hue), 15%, 96%); 44 | --color-button-hover: hsl(var(--rythm-hue), 100%, 90%); 45 | --color-button-active: hsl(var(--rythm-hue), 100%, 85%); 46 | --color-button-text: hsl(var(--rythm-hue), 15%, 15%); 47 | --color-button-ring: hsl(var(--rythm-hue), 100%, 70%); 48 | /* CTA */ 49 | /* --hue: 210; */ 50 | --hue: 210; 51 | --color-cta: hsl(var(--hue), 70%, 40%); 52 | --color-cta-hover: hsl(var(--hue), 70%, 55%); 53 | --color-cta-active: hsl(var(--hue), 70%, 60%); 54 | --color-cta-text: hsl(var(--hue), 10%, 100%); 55 | --color-cta-focus-ring: hsl(var(--hue), 100%, 100%); 56 | /* OPTIONS */ 57 | --option-hue: 40; 58 | --color-option: hsl(var(--option-hue), 50%, 83%); 59 | --color-option-hover: hsl(var(--option-hue), 50%, 86%); 60 | --color-option-active: hsl(var(--option-hue), 50%, 88%); 61 | --color-option-text: var(--color-text); 62 | --color-option-focus-ring: hsl(var(--option-hue), 50%, 50%); 63 | /* OPTIONS SELECTED */ 64 | --color-option-selected: hsl(var(--option-hue), 100%, 100%); 65 | --color-option-selected-text: hsl(var(--hue), 80%, 30%); 66 | /* FOCUS */ 67 | --color-focus-outline: hsl(var(--hue), 100%, 50%); 68 | /* BORDERS */ 69 | --color-border-light: hsl(var(--rythm-hue), 15%, 92%); 70 | --color-border-static: hsla(var(--rythm-hue), 100%, 15%, 15%); 71 | --color-border-field-hover: hsl(var(--rythm-hue), 80%, 80%); 72 | --color-border-field-active: hsl(var(--rythm-hue), 100%, 70%); 73 | /* SHADOWS */ 74 | --button-shadow: 0 0.25rem 1rem -0.5rem hsla(30, 15%, 15%, 20%); 75 | --main-shadow: 0 0 1rem -0.5rem hsla(30, 50%, 25%, 50%); 76 | } 77 | 78 | mr-text { 79 | font-family: 'Roboto'; 80 | line-height: 100%; 81 | font-size: 16px; 82 | } 83 | 84 | .mrjs { 85 | font-family: var(--font-title); 86 | font-size: 4vw; 87 | line-height: 100%; 88 | color: rgb(255, 255, 255); 89 | grid-column: 1; 90 | } 91 | 92 | .company { 93 | font-family: var(--font-title); 94 | font-size: 4vw; 95 | line-height: 100%; 96 | color: rgb(255, 255, 255); 97 | align-self: center; 98 | grid-column: 4; 99 | } 100 | 101 | .logo { 102 | grid-column: 3; 103 | z-index: 25; 104 | } 105 | 106 | #logo-model{ 107 | scale: 0.0005; 108 | } 109 | 110 | 111 | .layout { 112 | background-color: var(--color-paper); 113 | padding-top: 6vw; 114 | display: grid; 115 | grid-template-columns: 1fr 2fr 1fr; 116 | gap: 1vw; 117 | grid-auto-rows: auto; 118 | } 119 | 120 | .col-1{ 121 | grid-column: 1; 122 | } 123 | 124 | .col-2{ 125 | grid-column: 2; 126 | } 127 | 128 | .col-3{ 129 | grid-column: 3; 130 | } 131 | 132 | #navbar { 133 | background-color: #141414; 134 | border-radius: 1%; 135 | position:fixed; 136 | top: 0; 137 | left: 0; 138 | width: 100%; 139 | z-index: 4; 140 | display: grid; 141 | grid-template-columns: 1fr 2fr 0.5fr 0.5fr; 142 | padding: 10px; 143 | } 144 | 145 | .title { 146 | margin: 0 auto; 147 | font-family: var(--font-title); 148 | font-weight: 500; 149 | font-size: 8vw; 150 | line-height: 100%; 151 | color: rgba(24, 24, 24, 0.75); 152 | font-weight: bold; 153 | align-self: center; 154 | 155 | } 156 | 157 | .title-2 { 158 | font-family: var(--font-title); 159 | font-size: 5vw; 160 | margin-bottom: 1vh; 161 | line-height: 100%; 162 | color: rgba(24, 24, 24, 0.75); 163 | align-self: center; 164 | margin: 0 auto; 165 | } 166 | .subtitle { 167 | font-family: var(--font-title); 168 | font-size: 3vw; 169 | margin-bottom: 1vh; 170 | line-height: 100%; 171 | color: rgba(24, 24, 24, 0.75); 172 | } 173 | 174 | .label { 175 | font-family: var(--font-title); 176 | font-size: 2vw; 177 | line-height: 100%; 178 | color: rgba(24, 24, 24, 0.75); 179 | } 180 | 181 | .label-2 { 182 | font-family: var(--font-title); 183 | font-size: 1.5vw; 184 | line-height: 100%; 185 | color: rgba(24, 24, 24, 0.75); 186 | } 187 | 188 | .button { 189 | padding: 10px; 190 | margin: 10px; 191 | width: fit-content; 192 | } 193 | -------------------------------------------------------------------------------- /samples/examples/text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mr.js - text 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | mrjs 17 | 18 | 26 | 27 | volumetrics 28 | 29 | 30 | 31 | 32 | Text and TextInputs 33 | 34 | 35 | 36 | Text runs the internet, hence it being very important when it comes to web-library creation. It includes features such as readable text and input fields for user interaction. 37 | 38 | 39 | 40 | Examples 41 | 42 | 43 | 44 | mr-text 45 | 46 | 47 | 48 | Hi hello I'm a bunch of text. I'm on one line with no breaks 49 | 50 | Im on another line now. 51 | 52 | 53 | 54 | mr-text that should be `.innerText` updated by js 55 | 56 | 57 | 58 | Before I said this 59 | 60 | 61 | Before I said this 62 | 63 | 64 | 65 | mr-textarea 66 | 67 | Try typing in the below... 68 | 69 | 70 | 71 | 72 | mr-textfield 73 | 74 | Try typing in the below... 75 | 76 | 77 | 78 | 79 | Text 80 | 81 | 82 | 83 | Written pure text is achieved using the text component, `mr-text` with the text written within the tags (<...>words go here<.../>). 84 | 85 | 86 | 87 | TextArea 88 | 89 | 90 | 91 | TextArea is a multi-line input version of `mr-text`, extending from the MRTextInput class. It is achieved using the `mr-textarea` component and follows the usual textarea attributes. 92 | 93 | 94 | 95 | TextField 96 | 97 | 98 | 99 | TextField is a one-line input version of `mr-text`, extending from the MRTextInput class. It is achieved using the `mr-textfield` component and mimics the general 'input' html element. 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 114 | 115 | -------------------------------------------------------------------------------- /samples/examples/video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mr.js - video 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Play 16 | Stop 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 52 | 53 | -------------------------------------------------------------------------------- /samples/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /samples/index-assets/BricolageGrotesque.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/BricolageGrotesque.ttf -------------------------------------------------------------------------------- /samples/index-assets/BricolageGrotesque96pt-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/BricolageGrotesque96pt-SemiBold.ttf -------------------------------------------------------------------------------- /samples/index-assets/Onest-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/Onest-Bold.ttf -------------------------------------------------------------------------------- /samples/index-assets/Onest-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/Onest-Regular.ttf -------------------------------------------------------------------------------- /samples/index-assets/all-in-one.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/all-in-one.glb -------------------------------------------------------------------------------- /samples/index-assets/bowtie.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/bowtie.glb -------------------------------------------------------------------------------- /samples/index-assets/familiar.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/familiar.glb -------------------------------------------------------------------------------- /samples/index-assets/jigsaw.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/jigsaw.glb -------------------------------------------------------------------------------- /samples/index-assets/koifish.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/koifish.glb -------------------------------------------------------------------------------- /samples/index-assets/mrjs_logo.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/mrjs_logo.glb -------------------------------------------------------------------------------- /samples/index-assets/mrjs_logo_large.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/mrjs_logo_large.glb -------------------------------------------------------------------------------- /samples/index-assets/puzzle.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/puzzle.glb -------------------------------------------------------------------------------- /samples/index-style.css: -------------------------------------------------------------------------------- 1 | /* THIS IS AUTO GENERATED FROM scripts/update-all-submodules.sh ANY DIRECT EDITS WILL NOT BE SAVED */ 2 | @font-face { 3 | font-family: 'Onest'; 4 | src: url('./index-assets/Onest-Regular.ttf') format('truetype'); 5 | font-weight: 400; 6 | } 7 | 8 | @font-face { 9 | font-family: 'Bricolage'; 10 | src: url('./index-assets/BricolageGrotesque96pt-SemiBold.ttf') format('truetype'); 11 | font-weight: 700; 12 | } 13 | 14 | :root { 15 | --font-head: "Bricolage"; 16 | --font-body: "Onest"; 17 | 18 | --ink: #242424; 19 | --paper: white; 20 | --accent: aquamarine; 21 | --nav: #111; 22 | 23 | --space-around: 3vw; 24 | --space-between: 60px; 25 | --space-vertical: 12px; 26 | --height: 48px; 27 | --max-width: 600px; 28 | --max-width-h1: 840px; 29 | } 30 | 31 | html, 32 | body, mr-app, mr-panel { 33 | padding: 0; 34 | margin: 0; 35 | border: none; 36 | box-sizing: inherit; 37 | } 38 | 39 | mr-app { 40 | font-family: 'Onest'; 41 | line-height: 100%; 42 | font-size: 18px; 43 | } 44 | 45 | .nav { 46 | position: fixed; 47 | display: flex; 48 | flex-direction: row; 49 | top: var(--space-around); 50 | left: var(--space-around); 51 | width: calc(100% - var(--space-around) - var(--space-around)); 52 | height: var(--height); 53 | background-color: var(--nav); 54 | align-items: center; 55 | justify-content: center; 56 | padding: 0px 8px; 57 | border-radius: 24px; 58 | } 59 | 60 | mr-panel { 61 | display: flex; 62 | flex-flow: column nowrap; 63 | align-items: start; 64 | gap: var(--space-between); 65 | padding: var(--space-around) 3vw; 66 | background-color: var(--paper); 67 | } 68 | 69 | .section { 70 | display: flex; 71 | flex-direction: column; 72 | /* align-items: start; */ 73 | /* justify-content: center; */ 74 | letter-spacing: 1px; 75 | /* line-height: 1.25; */ 76 | line-height: 1.25; 77 | width: 100%; 78 | height: 100%; 79 | } 80 | 81 | .gapped { 82 | gap: var(--space-vertical); 83 | } 84 | 85 | .padded { 86 | padding: var(--space-around); 87 | } 88 | 89 | .narrow { 90 | max-width: 300px; 91 | } 92 | 93 | .diptych { 94 | display: grid; 95 | width: 100%; 96 | grid-template-columns: 1fr 1fr; 97 | /* grid-gap: 10px; */ 98 | justify-content: center; 99 | align-items: center; 100 | } 101 | 102 | .triptych { 103 | display: grid; 104 | width: 100%; 105 | grid-template-columns: 1fr 1fr 1fr; 106 | justify-content: start; 107 | align-items: start; 108 | } 109 | 110 | .illustration { 111 | display: flex; 112 | flex-flow: column nowrap; 113 | align-items: center; 114 | justify-content: center; 115 | width: 100%; 116 | height: 100%; 117 | min-height: 300px; 118 | background-color: var(--accent); 119 | border-radius: 10px; 120 | padding: 10px; 121 | } 122 | 123 | .documentation { 124 | background-color: var(--accent); 125 | width: fit-content; 126 | } 127 | 128 | .example-link { 129 | font-size: 2rem; 130 | } 131 | 132 | mr-text { 133 | letter-spacing: 1px; 134 | line-height: 100%; 135 | color: var(--ink); 136 | } 137 | 138 | .chapeau { 139 | font-size: 22px; 140 | max-width: 760px; 141 | } 142 | 143 | .h1 { 144 | max-width: var(--max-width-h1); 145 | font-family: "Bricolage"; 146 | font-size: 44px; 147 | font-weight: 700; 148 | padding-bottom: 20px; 149 | } 150 | 151 | .h2 { 152 | font-family: "Bricolage"; 153 | font-size: 32px; 154 | font-weight: 700; 155 | } 156 | 157 | .h3 { 158 | font-family: "Bricolage"; 159 | font-size: 24px; 160 | font-weight: 700; 161 | } 162 | 163 | .mosaic { 164 | display: grid; 165 | width: 100%; 166 | grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); 167 | gap: var(--space-around); 168 | justify-items: center; 169 | align-items: center; 170 | } 171 | 172 | mr-button { 173 | padding: 8px 16px; 174 | font-size: 18px; 175 | color: var(--ink); 176 | background-color: var(--accent); 177 | border-radius: 16px; 178 | } 179 | 180 | mr-button.hover { 181 | background-color: var(--nav); 182 | } 183 | 184 | @media only screen and (max-width: 56rem) { 185 | .h1 { 186 | font-size: 36px; 187 | } 188 | 189 | .diptych, .triptych { 190 | grid-template-columns: 1fr; 191 | } 192 | 193 | .first { 194 | order: -1; 195 | } 196 | } 197 | /* THIS IS AUTO GENERATED FROM scripts/update-all-submodules.sh ANY DIRECT EDITS WILL NOT BE SAVED */ 198 | -------------------------------------------------------------------------------- /samples/opengraph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/opengraph.jpg -------------------------------------------------------------------------------- /scripts/check-and-update-submodule.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################################################## 4 | ## !! IMPORTANT !! ## 5 | ## -- starts by stashing whatever local changes are made if any ## 6 | ## to not have issues with bumping the submodule commit. ## 7 | ## -- if submodule needs an update, then updates it and ## 8 | ## commits the changes in this main repo. ## 9 | ## -- applies the stashed changes on any type of exit (error, ## 10 | ## success, etc) to put the user back in their original ## 11 | ## state. ## 12 | ################################################################## 13 | 14 | # Function to handle script exit without needing an update 15 | cleanup_success() { 16 | apply_stash 17 | exit 0 18 | } 19 | 20 | # Function to handle script exit with an update 21 | cleanup_update() { 22 | apply_stash 23 | exit 2 24 | } 25 | 26 | # Function to handle unexpected errors 27 | error_handler() { 28 | echo "An error occurred. Cleaning up..." 29 | apply_stash 30 | exit 1 31 | } 32 | 33 | # Function to apply stashed changes 34 | apply_stash() { 35 | if [ "$STASH_APPLIED" = true ]; then 36 | echo "Applying stashed changes..." 37 | cd "$REPO_DIR" || exit 1 38 | git stash apply 39 | echo "Stashed changes have been reapplied." 40 | fi 41 | } 42 | 43 | # Check if a path argument was provided 44 | if [ "$#" -ne 1 ]; then 45 | echo "Usage: $0 path/to/your/submodule" 46 | exit 1 47 | fi 48 | 49 | echo "HI4" 50 | SUBMODULE_DIR="$1" 51 | echo "HI5" 52 | REPO_DIR=$(pwd) 53 | echo "HI6" 54 | 55 | trap error_handler ERR 56 | echo "HI6_0" 57 | trap cleanup_success EXIT 58 | echo "HI6_1" 59 | 60 | STASH_OUTPUT=$(git stash push -m "Auto-stashed by submodule update script") 61 | echo "HI6_2" 62 | if [[ "$STASH_OUTPUT" == *"No local changes to save"* ]]; then 63 | STASH_APPLIED=false 64 | else 65 | STASH_APPLIED=true 66 | fi 67 | 68 | echo "HI7" 69 | 70 | cd "$SUBMODULE_DIR" || exit 1 71 | 72 | git fetch origin 73 | 74 | LATEST_COMMIT=$(git rev-parse origin/main) 75 | CURRENT_COMMIT=$(git rev-parse HEAD) 76 | 77 | echo "HI8" 78 | 79 | if [ "$LATEST_COMMIT" == "$CURRENT_COMMIT" ]; then 80 | echo "Submodule $SUBMODULE_DIR is up to date." 81 | else 82 | echo "Submodule $SUBMODULE_DIR is not at the latest commit. Updating..." 83 | git checkout $LATEST_COMMIT 84 | cd "$REPO_DIR" 85 | git add "$SUBMODULE_DIR" 86 | git commit -m "Updated submodule $SUBMODULE_DIR to its latest repo commit: $LATEST_COMMIT" 87 | echo "Submodule updated and committed." 88 | 89 | # Change the trap for EXIT to use the cleanup_update function 90 | trap cleanup_update EXIT 91 | fi 92 | -------------------------------------------------------------------------------- /scripts/check-if-submodule-needs-update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if a path argument was provided 4 | if [ "$#" -ne 1 ]; then 5 | echo "Usage: $0 path/to/your/submodule" 6 | exit 1 7 | fi 8 | 9 | SUBMODULE_DIR="$1" 10 | REPO_DIR=$(pwd) 11 | 12 | # Function to apply stashed changes and exit 13 | cleanup() { 14 | cd "$REPO_DIR" || exit 1 15 | STASH_OUTPUT=$(git stash list) 16 | if [[ $STASH_OUTPUT == *"Auto-stashed by submodule update script"* ]]; then 17 | echo "Applying stashed changes..." 18 | git stash pop 19 | echo "Stashed changes have been reapplied." 20 | fi 21 | exit "$1" 22 | } 23 | 24 | # Handle unexpected errors 25 | trap 'cleanup 1' ERR 26 | 27 | # Stash local changes if any 28 | STASH_OUTPUT=$(git stash push -m "Auto-stashed by submodule update script") 29 | cd "$SUBMODULE_DIR" || exit 1 30 | 31 | git fetch origin 32 | 33 | LATEST_COMMIT=$(git rev-parse origin/main) 34 | CURRENT_COMMIT=$(git rev-parse HEAD) 35 | 36 | if [ "$LATEST_COMMIT" == "$CURRENT_COMMIT" ]; then 37 | echo "Submodule $SUBMODULE_DIR is up to date." 38 | cleanup 0 39 | else 40 | echo "Submodule $SUBMODULE_DIR is not at the latest commit. Please update." 41 | cleanup 2 42 | fi 43 | -------------------------------------------------------------------------------- /scripts/create-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define base output directories 4 | output_dir='docs' 5 | js_api_dir="${output_dir}/js-api" 6 | js_api_utils_dir="${output_dir}/js-api-utils" 7 | js_api_extras_dir="${output_dir}/js-api-extras" 8 | 9 | # Remove the existing output directories if they exist and create new ones 10 | rm -rf "$js_api_dir" "$js_api_utils_dir" "$js_api_extras_dir" 11 | mkdir -p "$js_api_dir" "$js_api_utils_dir" "$js_api_extras_dir" 12 | 13 | # Function to process .js files 14 | process_js_files() { 15 | local source_dir=$1 16 | local output_dir=$2 17 | local ignore_index_js_subdirs=$3 18 | local counter=0 # Local counter for each call 19 | 20 | # Construct the base find command to exclude 'index.js' files directly 21 | local find_cmd="find \"$source_dir\" -type f -name '*.js' ! -name 'index.js' ! -name '_*'" 22 | 23 | # If ignoring subdirectories with index.js 24 | if [[ "$ignore_index_js_subdirs" == "true" ]]; then 25 | local ignore_dirs=$(find "$source_dir" -mindepth 2 -type f -name 'index.js' -exec dirname {} \; | sort | uniq) 26 | for dir in $ignore_dirs; do 27 | local escaped_dir=$(printf '%q' "$dir") 28 | find_cmd+=" ! -path \"$escaped_dir/*\"" 29 | done 30 | fi 31 | 32 | eval "$find_cmd" | while read file; do 33 | # Format the counter with leading zeros 34 | printf -v formatted_counter "%03d" $counter 35 | 36 | # Generate new Markdown file name by prepending the counter to the base name of the .js file 37 | md_file="${output_dir}/${formatted_counter}$(basename "${file%.js}.md")" 38 | 39 | # Extract the title from the Markdown file name, accurately reflecting the original file name 40 | local original_file_name=$(basename "$file" .js) 41 | local title="${original_file_name}" 42 | 43 | # Generate Markdown content 44 | { 45 | echo '---' 46 | echo "title: ${title}" 47 | echo "github-path: https://github.com/volumetrics-io/mrjs/edit/main/${file}" 48 | echo '---' 49 | echo "# ${title}" 50 | echo '' 51 | jsdoc2md "$file" 52 | } > "$md_file" 53 | 54 | # Increment the local counter 55 | ((counter++)) 56 | done 57 | } 58 | 59 | # Process files for each directory 60 | process_js_files "src" "$js_api_dir" true # true to ignore subdirs with index.js for js-api 61 | process_js_files "src/utils" "$js_api_utils_dir" false 62 | process_js_files "src/extras" "$js_api_extras_dir" false 63 | -------------------------------------------------------------------------------- /src/core/MRElement.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class MRElement 3 | * @classdesc The first step in MRjs extending an HTMLElement. Used as a base for both `mr-app` and `mr-entity`. 4 | * @augments HTMLElement 5 | */ 6 | export class MRElement extends HTMLElement { 7 | /** 8 | * @class 9 | * @description Constructs the basic information needed to separate an `MRElement` from an `HTMLElement`. 10 | */ 11 | constructor() { 12 | super(); 13 | this.environment = null; 14 | this.observer = null; 15 | 16 | // TODO: find alternative solution. This breaks with the switch to asychronous entity initialization @takahirox 17 | // Hack for the performance. 18 | // Element.getBoundingClientRect() is called from many places 19 | // mainly to sync the layout between DOM elements and 3D scene. 20 | // But .getBoundingClientRect() is slow because it may invoke reflow. 21 | // To avoid reflow in sync we use a hack with IntersectionObserver 22 | // that gives .boundingClientRect in callback function. 23 | // 24 | // There are some concerns in this approach. 25 | // 1. It might be costly to apply this technique to all MREntities. 26 | // Optimization might be necessary to apply it only to MREntities where 27 | // .getBoundingClientRect() is frequently executed. 28 | // 2. Since boundingClientRect is updated asynchronously, the latest values 29 | // may be set a few frames late. It is necessary to confirm whether this 30 | // is acceptable for the user experience and various systems. One possible 31 | // workaround, which is not perfect but simple, would be to add a new method 32 | // that is more efficient but may not return the latest value, in addition to 33 | // the regular .getBoundingClientRect() method. The caller can then choose 34 | // which method to use depending on the purpose. 35 | // 3. Asynchronous updates can make testing, problem investigation, and debugging 36 | // more difficult. For example, if a bug occurs only when updates are delayed by 37 | // greater than a certain number of frames, it may be difficult to reproduce the 38 | // issue and making problem investigation very challenging. (If IntersectionObserver 39 | // specification guarantees that the callback would always be called during the next 40 | // idle time after .observe() is executed, the problem would be less significant.) 41 | this._boundingClientRect = null; 42 | } 43 | 44 | /** 45 | * @function 46 | * @description Adding an entity as a sub-object of this entity. 47 | * @param {object} entity - the entity to be added. 48 | */ 49 | add(entity) {} 50 | 51 | /** 52 | * @function 53 | * @description Removing an entity as a sub-object of this entity. 54 | * @param {object} entity - the entity to be removed. 55 | */ 56 | removeEntity(entity) {} 57 | 58 | // TODO: find alternative solution. This breaks with the switch to asychronous entity initialization 59 | // /** 60 | // * @function 61 | // * @description Overrides getBoundingClientRect() to avoid reflow in sync as optimization 62 | // * @returns {object} rect - the bounding client rect of the HTMLElement representation of this MRElement. 63 | // */ 64 | // getBoundingClientRect() { 65 | // // This is a fallback in case if .getBoundingClientRect() is called before 66 | // // ._boundingClientRect is initialized. 67 | // if (this._boundingClientRect === null) { 68 | // this._boundingClientRect = super.getBoundingClientRect(); 69 | // } 70 | // // Assuming the values in the return value object are not overridden in the callers. 71 | // // If it happens, it affects to all the callers until ._boundingClientRect is refreshed. 72 | // return this._boundingClientRect; 73 | // } 74 | } 75 | -------------------------------------------------------------------------------- /src/core/componentSystems/AudioSystem.js: -------------------------------------------------------------------------------- 1 | import { MRSystem } from 'mrjs/core/MRSystem'; 2 | 3 | /** 4 | * @class AudioSystem 5 | * @classdesc This system manages spatial audio in the THREE.js scene. 6 | * @augments MRSystem 7 | */ 8 | export class AudioSystem extends MRSystem { 9 | /** 10 | * @class 11 | * @description AudioSystem's Default constructor that sets up the audio listener and loader 12 | */ 13 | constructor() { 14 | super(); 15 | 16 | this.listener = new THREE.AudioListener(); 17 | this.app.scene.add(this.listener); 18 | 19 | this.audioLoader = new THREE.AudioLoader(); 20 | } 21 | 22 | /** 23 | * @function 24 | * @description The generic system update call. Updates the clipped view of every entity in this system's registry. 25 | * @param {number} deltaTime - given timestep to be used for any feature changes 26 | * @param {object} frame - given frame information to be used for any feature changes 27 | */ 28 | update(deltaTime, frame) { 29 | this.listener.position.setFromMatrixPosition(this.app.user.origin.matrixWorld); 30 | this.listener.setRotationFromMatrix(this.app.user.origin.matrixWorld); 31 | } 32 | 33 | /** 34 | * @function 35 | * @description Called when the entity component is initialized 36 | * @param {object} entity - the entity being attached/initialized. 37 | */ 38 | attachedComponent(entity) { 39 | entity.sound = new THREE.PositionalAudio(this.listener); 40 | 41 | entity.object3D.add(entity.sound); 42 | 43 | let comp = entity.components.get('audio'); 44 | 45 | this.audioLoader.load(comp.src, (buffer) => { 46 | entity.sound.setBuffer(buffer); 47 | entity.sound.setRefDistance(comp.distance ?? 1); 48 | 49 | this.setAudioState(entity, comp); 50 | }); 51 | } 52 | 53 | /** 54 | * @function 55 | * @description Called when the entity component is updated 56 | * @param {object} entity - the entity being updated based on the component. 57 | */ 58 | updatedComponent(entity) { 59 | let comp = entity.components.get('audio'); 60 | entity.sound.setRefDistance(comp.distance ?? 1); 61 | this.setAudioState(entity, comp); 62 | } 63 | 64 | /** 65 | * @function 66 | * @description Called when the entity component is detached 67 | * @param {object} entity - the entity being updated based on the component being detached. 68 | */ 69 | detachedComponent(entity) { 70 | entity.sound.stop(); 71 | entity.sound.dispose(); 72 | entity.sound = null; 73 | } 74 | 75 | /** 76 | * @function 77 | * @description Updates the Audio State based on the user passed 'state' variable. 78 | * @param {object} entity - the entity being updated based on the component being detached. 79 | * @param {object} comp - component that contains the value of 'action' 80 | */ 81 | setAudioState(entity, comp) { 82 | entity.sound.setLoop(comp.loop ?? false); 83 | switch (comp.action) { 84 | case 'play': 85 | if (entity.sound.isPlaying) { 86 | entity.sound.stop(); 87 | } 88 | entity.sound.play(); 89 | break; 90 | case 'pause': 91 | entity.sound.pause(); 92 | case 'stop': 93 | default: 94 | entity.sound.stop(); 95 | break; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/core/componentSystems/BoundaryVisibilitySystem.js: -------------------------------------------------------------------------------- 1 | import { MRSystem } from 'mrjs/core/MRSystem'; 2 | import { MRDivEntity } from 'mrjs/core/entities/MRDivEntity'; 3 | import { MRPanelEntity } from 'mrjs/core/entities/MRPanelEntity'; 4 | 5 | /** 6 | * @function 7 | * @description Observe a target MRDivEntity and make the associated object visible only if it is in visible position in a root MRDivEntity 8 | * @param {MRDivEntity} root - the root object being compared against 9 | * @param {MRDivEntity} target - the target object for which we're determining visiblity. 10 | * @returns {IntersectionObserver} - an observer for tracking visiblity 11 | */ 12 | const observe = (root, target) => { 13 | // TODO: Callback is fired asynchronously so no guaranteed to be called immediately when the 14 | // visibility from the layout position changes. Therefore, the visibility of the associated 15 | // Object3D's might be updated a few frames later after when it really need to be. It might 16 | // affect the user experience. Fix it if possible. 17 | const observer = new IntersectionObserver( 18 | (entries) => { 19 | for (const entry of entries) { 20 | // TODO: Ensure to avoid the visibility set up collision. If multiple systems set up the visibility 21 | // independently, only the last system before render call can have an effect. This callback 22 | // is fired asynchronously so it may be said that this callback is executed before all the 23 | // sync systems. 24 | entry.target.object3D.visible = entry.intersectionRatio > 0; 25 | } 26 | // Somehow callback is sometimes not fired even though crossing the threshold so as fallback 27 | // always refresh the info as much as possible to keep it up-to-date. 28 | // It seems that the callback is always called once soon after observe() is called, 29 | // regardless of the intersection state of the entity. 30 | // TODO: Confirm whether this behavior is intended. If it is not, there may be future 31 | // behavior changes or it may not work as intended on certain platforms. 32 | // Alternative: Using multi-step threshold would mitigate the problem like [0.0, 0.05, 0.1] 33 | observer.disconnect(); 34 | observer.observe(target); 35 | }, 36 | { 37 | root: root, 38 | } 39 | ); 40 | observer.observe(target); 41 | return observer; 42 | }; 43 | 44 | /** 45 | * @class BoundaryVisibilitySystem 46 | * @classdesc Makes the entities invisible if they are outside of their parent panels 47 | * @augments MRSystem 48 | */ 49 | export class BoundaryVisibilitySystem extends MRSystem { 50 | /** 51 | * @class 52 | * @description BoundaryVisibilitySystem's default constructor. 53 | */ 54 | constructor() { 55 | super(false); 56 | this.observedEntities = new WeakSet(); 57 | this.observers = new Map(); 58 | } 59 | 60 | /** 61 | * @function 62 | * @description Called when a new entity is added to the scene. 63 | * @param {object} entity - the entity being added. 64 | */ 65 | onNewEntity(entity) { 66 | // TODO: Support nested panels 67 | if (entity instanceof MRPanelEntity) { 68 | this.registry.add(entity); 69 | entity.traverse((child) => { 70 | if (child === entity) { 71 | return; 72 | } 73 | 74 | if (this.observedEntities.has(child)) { 75 | return; 76 | } 77 | 78 | this.observedEntities.add(child); 79 | 80 | this.observers.set(child, observe(entity, child)); 81 | }); 82 | } else if (!this.observedEntities.has(entity) && entity instanceof MRDivEntity) { 83 | // There is a chance that a child entity is added after parent panel addition. 84 | // Check registered panels and set up the observer if panels are found in parents. 85 | for (const panel of this.registry) { 86 | if (panel.contains(entity)) { 87 | this.observedEntities.add(entity); 88 | this.observers.set(child, observe(panel, entity)); 89 | break; 90 | } 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * @function 97 | * @description Called when an entity is removed from the scene. 98 | * @param {object} entity - the entity being added. 99 | */ 100 | _entityRemoved(entity) { 101 | if (entity instanceof MRPanelEntity) { 102 | this.registry.delete(entity); 103 | entity.traverse((child) => { 104 | if (this.observedEntities.has(child)) { 105 | this.observedEntities.delete(child); 106 | this.observers.get(child).unobserve(child); 107 | this.observers.delete(child); 108 | } 109 | }); 110 | } else if (this.observedEntities.has(entity)) { 111 | this.observedEntities.delete(entity); 112 | this.observers.get(entity).unobserve(entity); 113 | this.observers.delete(entity); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/core/componentSystems/InstancingSystem.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | import { MRSystem } from 'mrjs/core/MRSystem'; 4 | import { MREntity } from 'mrjs/core/MREntity'; 5 | 6 | /** 7 | * @class InstancingSystem 8 | * @classdesc System that allows for instancing of meshes based on a given entity where the instances can be modified separately. 9 | * @augments MRSystem 10 | */ 11 | export class InstancingSystem extends MRSystem { 12 | /** 13 | * @class 14 | * @description InstancingSystem's default constructor that sets up default instancing count, transformations, and mesh information. 15 | */ 16 | constructor() { 17 | super(); 18 | 19 | this.instanceCount = 5; 20 | this.transformations = []; 21 | this.instancedMesh = null; 22 | } 23 | 24 | /** 25 | * @function 26 | * @description The generic system update call. Updates the entity and its instances to their appropriate transformations and visuals 27 | * based on the picked predefined option. 28 | * @param {number} deltaTime - given timestep to be used for any feature changes 29 | * @param {object} frame - given frame information to be used for any feature changes 30 | */ 31 | update(deltaTime, frame) { 32 | for (const entity of this.registry) { 33 | switch (entity.components.get('instancing')?.type) { 34 | case 'random': 35 | this.random(entity); 36 | break; 37 | 38 | default: 39 | break; 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * @function 46 | * @description Determines what meshes are attached from this entity and When a component is attached. 47 | * Setups up instancing based on the predefined setup option and the entity's geometry (handling properly whether that be a group or mesh). 48 | * @param {MREntity} entity - the entity with the geometry to be instanced and the chosen setup option 49 | */ 50 | attachedComponent(entity) { 51 | // ----- setup for instanced geometry ----- 52 | 53 | let originalMesh = entity.object3D; 54 | let combinedGeometry = new THREE.BufferGeometry(); 55 | 56 | // grab usable mesh 57 | if (originalMesh instanceof THREE.Mesh) { 58 | combinedGeometry = originalMesh.geometry.clone(); 59 | } else if (originalMesh instanceof THREE.Group) { 60 | originalMesh.traverse((child) => { 61 | if (child instanceof THREE.Mesh) { 62 | const geometry = child.geometry.clone(); 63 | geometry.applyMatrix4(child.matrixWorld); // Apply the child's world matrix 64 | combinedGeometry.merge(geometry); 65 | } 66 | }); 67 | } 68 | 69 | // ----- create instances information ----- 70 | 71 | // Setup for the to-be-used instance 72 | const instancedGeometry = new THREE.InstancedBufferGeometry(); 73 | instancedGeometry.copy(combinedGeometry); 74 | for (let i = 0; i < this.instanceCount; ++i) { 75 | const matrix = new THREE.Matrix4(); 76 | matrix.makeTranslation(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5); 77 | 78 | this.transformations.push(matrix); 79 | } 80 | 81 | // ----- add instances to scene ----- 82 | 83 | // Create an InstancedMesh using the instanced geometry and matrices 84 | const material = new THREE.MeshBasicMaterial({ color: 0xffff00 }); 85 | const instancedMesh = new THREE.InstancedMesh(instancedGeometry, material, this.instanceCount); 86 | instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage); 87 | 88 | // Set matrices for instances 89 | for (let i = 0; i < this.instanceCount; ++i) { 90 | instancedMesh.setMatrixAt(i, this.transformations[i]); 91 | } 92 | instancedMesh.instanceMatrix.needsUpdate = true; 93 | this.instancedMesh = instancedMesh; 94 | 95 | // Add the instanced mesh to the scene 96 | entity.object3D.add(instancedMesh); 97 | } 98 | 99 | /************ Some options for default instancing setup ************/ 100 | 101 | /** 102 | * @function 103 | * @description An option for default instancing. Places the given entity instancing it at a bunch of random transformation locations.Uses threejs's `InstancedMesh`. 104 | * @param {MREntity} entity - the entity to be instanced in random locations 105 | */ 106 | random = (entity) => { 107 | // update mesh for each instance 108 | for (let i = 0; i < this.instanceCount; ++i) { 109 | const matrix = new THREE.Matrix4(); 110 | matrix.makeScale(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5); 111 | matrix.makeRotation(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5); 112 | matrix.makeTranslation(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5); 113 | 114 | this.transformations[i].copy(matrix); 115 | this.instancedMesh.setMatrixAt(i, this.transformations[i]); 116 | } 117 | }; 118 | } 119 | -------------------------------------------------------------------------------- /src/core/componentSystems/LayoutSystem.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | import { MRSystem } from 'mrjs/core/MRSystem'; 4 | import { MREntity } from 'mrjs/core/MREntity'; 5 | import { MRDivEntity } from 'mrjs/core/entities/MRDivEntity'; 6 | import { MRPanelEntity } from 'mrjs/core/entities/MRPanelEntity'; 7 | 8 | /** 9 | * @class LayoutSystem 10 | * @classdesc System that allows for setup and handling of changing layout. 11 | * @augments MRSystem 12 | */ 13 | export class LayoutSystem extends MRSystem { 14 | /** 15 | * @class 16 | * @description Constructor for the layout system. Uses the default System setup. 17 | */ 18 | constructor() { 19 | super(false); 20 | this.tempPosition = new THREE.Vector3(); 21 | } 22 | 23 | /** 24 | * @function 25 | * @description Called when a new entity is added to this system 26 | * @param {MREntity} entity - the entity being added. 27 | */ 28 | onNewEntity(entity) { 29 | if (entity instanceof MRPanelEntity) { 30 | return; 31 | } 32 | if (entity instanceof MRDivEntity) { 33 | this.registry.add(entity); 34 | this.setLayoutPosition(entity); 35 | } 36 | } 37 | 38 | /** 39 | * @function 40 | * @description The generic system update call. Handles updating all 3D items to match whatever layout position is expected. 41 | * @param {number} deltaTime - given timestep to be used for any feature changes 42 | * @param {object} frame - given frame information to be used for any feature changes 43 | */ 44 | update(deltaTime, frame) { 45 | for (const entity of this.registry) { 46 | this.setLayoutPosition(entity); 47 | } 48 | } 49 | 50 | /** 51 | * @function 52 | * @description Helper function for the update call. Sets the entity's appropriate 3D layout position based on window and entity expectations. 53 | * @param {MREntity} entity - the entity being updated. 54 | */ 55 | setLayoutPosition(entity) { 56 | const rect = entity.getBoundingClientRect(); 57 | 58 | const panel = entity.closest('mr-panel'); 59 | if (!panel) { 60 | return; 61 | } 62 | const panelRect = panel.getBoundingClientRect(); 63 | 64 | /** setup xy positioning of the entity */ 65 | 66 | let innerWidth = parseFloat(panel.compStyle.width.split('px')[0]); 67 | let innerHeight = parseFloat(panel.compStyle.height.split('px')[0]); 68 | let centerX = innerWidth / 2; 69 | let centerY = innerHeight / 2; 70 | 71 | let windowWidth = panel.width; 72 | let windowHeight = panel.height; 73 | let centeredX = rect.left - panelRect.left - centerX; 74 | let centeredY = rect.top - panelRect.top - centerY; 75 | 76 | let threeX = (centeredX / innerWidth) * windowWidth; 77 | let threeY = (centeredY / innerHeight) * windowHeight; 78 | 79 | threeX += entity.width / 2; 80 | threeY += entity.height / 2; 81 | 82 | entity.object3D.position.setX(threeX); 83 | entity.object3D.position.setY(-threeY); 84 | 85 | /** setup z-index positioning of the entity */ 86 | 87 | if (entity.compStyle.zIndex != 'auto') { 88 | // default zIndex values in css are in the 1000s - using this arbitrary divide to convert to an actual usable threejs value. 89 | entity.object3D.position.setZ(parseFloat(entity.compStyle.zIndex) / 1000); 90 | 91 | if (entity.compStyle.zIndex == entity.parentElement.compStyle.zIndex) { 92 | entity.object3D.position.z += 0.0001; 93 | } 94 | } else { 95 | entity.object3D.position.z = entity.parentElement.object3D.position.z + 0.001; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/core/componentSystems/MaterialStyleSystem.js: -------------------------------------------------------------------------------- 1 | import { MRSystem } from 'mrjs/core/MRSystem'; 2 | import { MRDivEntity } from 'mrjs/core/entities/MRDivEntity'; 3 | import { MREntity } from 'mrjs/core/MREntity'; 4 | 5 | /** 6 | * @class MaterialStyleSystem 7 | * @classdesc Handles style updates for all items. 8 | * @augments MRSystem 9 | */ 10 | export class MaterialStyleSystem extends MRSystem { 11 | /** 12 | * @class 13 | * @description StyleSystem's default constructor with a starting framerate of 1/30. 14 | */ 15 | constructor() { 16 | super(false); 17 | 18 | this.app.addEventListener('trigger-material-style-update', (e) => { 19 | // The event has the entity stored as its detail. 20 | if (e.detail !== undefined) { 21 | this._updateSpecificEntity(e.detail); 22 | } 23 | }); 24 | } 25 | 26 | /** 27 | * @function 28 | * @param {object} entity - the MREntity being updated. 29 | * @description The per entity triggered update call. Handles updating all 3D items to match whatever geometry/style is expected whether that be a 2D setup or a 3D change. 30 | */ 31 | _updateSpecificEntity(entity) { 32 | // Anything needed for mrjs defined entities - the order of the below matters 33 | if (entity instanceof MRDivEntity) { 34 | this.setBackground(entity); 35 | } 36 | this.setVisibility(entity); 37 | 38 | // User additional - Main Entity Style Change 39 | if (entity instanceof MREntity) { 40 | entity.updateMaterialStyle(); 41 | } 42 | } 43 | 44 | /** 45 | * @function 46 | * @description The per-frame system update call. Handles updating all 3D items to match whatever geometry/style is expected whether that be a 2D setup or a 3D change. 47 | * @param {number} deltaTime - given timestep to be used for any feature changes 48 | * @param {object} frame - given frame information to be used for any feature changes 49 | */ 50 | update(deltaTime, frame) { 51 | // For this system, since we have the 'per entity' and 'per scene event' update calls, 52 | // we dont need a main update call here. 53 | for (const entity of this.registry) { 54 | this._updateSpecificEntity(entity); 55 | } 56 | } 57 | 58 | /** 59 | * @function 60 | * @description Called when a new entity is added to the scene. Adds said new entity to the style's system registry. 61 | * @param {object} entity - the MREntity being touched by this function. 62 | */ 63 | onNewEntity(entity) { 64 | this.registry.add(entity); 65 | } 66 | 67 | /** 68 | * @function 69 | * @param {object} entity - the MREntity being updated. 70 | * @description Sets the background based on compStyle and inputted css elements. 71 | */ 72 | setBackground(entity) { 73 | mrjsUtils.color.setObject3DColor(entity.background, entity.compStyle.backgroundColor, entity.compStyle.opacity); 74 | } 75 | 76 | /** 77 | * @function 78 | * @description Sets the visibility of the MREntity based on its css 'visibility' property. 79 | * @param {object} entity - the MREntity being updated. 80 | */ 81 | setVisibility(entity) { 82 | if (entity.compStyle.visibility && entity.compStyle.visibility !== 'none' && entity.compStyle.visibility !== 'collapse') { 83 | // visbility: hidden or visible are the options we care about 84 | const isVisible = entity.compStyle.visibility !== 'hidden'; 85 | entity.object3D.visible = isVisible; 86 | if (entity.background) { 87 | // The background for MRDivEntity, but we want this css property allowed 88 | // for all, so using this checker to confirm the existence first. 89 | // entity.background.visible = bool; 90 | // 91 | // XXX - right now all backgrounds are set as visible=false by default in their 92 | // MRDivEntity constructors, so toggling them here isnt useful, but in future 93 | // if this is requested for use or we want to add a feature for more use of the 94 | // background - adding in toggling for this with the object will be useful. 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/core/componentSystems/PanelSystem.js: -------------------------------------------------------------------------------- 1 | import { MRSystem } from 'mrjs/core/MRSystem'; 2 | import { MRPanelEntity } from 'mrjs/core/entities/MRPanelEntity'; 3 | 4 | import { mrjsUtils } from 'mrjs'; 5 | 6 | /** 7 | * @class PanelManagementSystem 8 | * @classdesc A system that manages the screen relative position of UI panels 9 | * @augments MRSystem 10 | */ 11 | export class PanelSystem extends MRSystem { 12 | /** 13 | * @class 14 | * @description Constructor for the PanelManagementSystem system. Uses the default System setup. 15 | */ 16 | constructor() { 17 | super(false); 18 | } 19 | 20 | eventUpdate = () => { 21 | for (const entity of this.registry) { 22 | entity.panel.scale.setScalar(mrjsUtils.app.scale); 23 | } 24 | }; 25 | 26 | /** 27 | * @function 28 | * @description The generic system update call. keeps panel positions up to date. 29 | * @param {number} deltaTime - given timestep to be used for any feature changes 30 | * @param {object} frame - given frame information to be used for any feature changes 31 | */ 32 | update(deltaTime, frame) { 33 | for (const entity of this.registry) { 34 | this.updatePanel(entity); 35 | } 36 | } 37 | 38 | /** 39 | * @function 40 | * @description Called when a new entity is added to this system 41 | * @param {object} entity - the entity being added. 42 | */ 43 | onNewEntity(entity) { 44 | if (entity instanceof MRPanelEntity) { 45 | this.registry.add(entity); 46 | } 47 | } 48 | 49 | /** 50 | * @function 51 | * @description used to set the position of an individual panel 52 | * @param {object} entity - the entity being updated. 53 | */ 54 | updatePanel(entity) { 55 | const rect = entity.getBoundingClientRect(); 56 | const appRect = this.app.getBoundingClientRect(); 57 | 58 | /** setup xy positioning of the entity */ 59 | 60 | let innerWidth = global.appWidth; 61 | let innerHeight = global.appHeight; 62 | let centerX = innerWidth / 2; 63 | let centerY = innerHeight / 2; 64 | 65 | let windowWidth = global.viewPortWidth; 66 | let windowHeight = global.viewPortHeight; 67 | 68 | let top = rect.top - appRect.top; 69 | let left = rect.left - appRect.left; 70 | if (mrjsUtils.xr.isPresenting) { 71 | top = (top / window.screen.height) * mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION; 72 | left = (left / window.innerWidth) * mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION; 73 | } 74 | let centeredX = left - centerX; 75 | let centeredY = top - centerY; 76 | 77 | let threeX = (centeredX / innerWidth) * windowWidth; 78 | let threeY = (centeredY / innerHeight) * windowHeight; 79 | 80 | threeX += entity.width / 2; 81 | threeY += entity.height / 2; 82 | 83 | entity.panel.position.setX(threeX); 84 | entity.panel.position.setY(-threeY); 85 | 86 | /** setup z-index positioning of the entity */ 87 | 88 | if (entity.compStyle.zIndex != 'auto') { 89 | // default zIndex values in css are in the 1000s - using this arbitrary divide to convert to an actual usable threejs value. 90 | entity.panel.position.setZ(parseFloat(entity.compStyle.zIndex) / 1000); 91 | 92 | if (entity.compStyle.zIndex == entity.parentElement.compStyle.zIndex) { 93 | entity.panel.position.z += 0.0001; 94 | } 95 | } else { 96 | entity.panel.position.z = 0; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/core/componentSystems/SkyBoxSystem.js: -------------------------------------------------------------------------------- 1 | import { MRSystem } from 'mrjs/core/MRSystem'; 2 | import { MRSkyBoxEntity } from 'mrjs/core/entities/MRSkyBoxEntity'; 3 | 4 | /** 5 | * @class SkyBoxSystem 6 | * @classdesc Handles skybox interactions and updates for all items. 7 | * @augments MRSystem 8 | */ 9 | export class SkyBoxSystem extends MRSystem { 10 | /** 11 | * @class 12 | * @description SkyBox's default constructor 13 | */ 14 | constructor() { 15 | super(false); 16 | 17 | // used as a helper because we cant grab the most recently added 18 | // item from registry since it's a set. 19 | this._lastItem = null; 20 | } 21 | 22 | /** 23 | * @function 24 | * @description The generic system update call. 25 | * @param {number} deltaTime - given timestep to be used for any feature changes 26 | * @param {object} frame - given frame information to be used for any feature changes 27 | */ 28 | update(deltaTime, frame) { 29 | // leave for when needed. 30 | } 31 | 32 | /** 33 | * @function 34 | * @description Called when a new entity is added to the scene. Adds said new entity to the style's system registry. 35 | * @param {object} entity - the entity being added. 36 | */ 37 | onNewEntity(entity) { 38 | if (entity instanceof MRSkyBoxEntity) { 39 | if (entity.compStyle.scale == 'none') { 40 | // has no css scale attribute then use as default otherwise use as the user-defined version. 41 | const SCALING_OFFSET = 0.001; 42 | entity.object3D.scale.setScalar(this.registry.size == 0 ? 1 : this._lastItem.object3D.scale.z + SCALING_OFFSET); 43 | this._lastItem = entity; 44 | } 45 | this.registry.add(entity); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/core/componentSystems/StatsSystem.js: -------------------------------------------------------------------------------- 1 | import { MREntity } from 'mrjs/core/MREntity'; 2 | import { MRSystem } from 'mrjs/core/MRSystem'; 3 | import { MRStatsEntity } from 'mrjs/core/entities/MRStatsEntity'; 4 | 5 | const REFRESH_SEC = 1.0; 6 | 7 | /** 8 | * @class StatsSystem 9 | * @classdesc Track the elapsed time across frames and update the fps counter periodically for `mr-stats`. 10 | * @augments MRSystem 11 | */ 12 | export class StatsSystem extends MRSystem { 13 | /** 14 | * @class 15 | * @description StatsSystem's default constructor 16 | */ 17 | constructor() { 18 | super(false); 19 | } 20 | 21 | /** 22 | * @function 23 | * @description Registers MRStatsEntity 24 | * @param {MREntity} entity - given entity that might be handled by this system 25 | */ 26 | onNewEntity(entity) { 27 | if (entity instanceof MRStatsEntity) { 28 | this.registry.add(entity); 29 | } 30 | } 31 | 32 | /** 33 | * @function 34 | * @description Tracks the elapsed time and updates the fps counter periodically. 35 | * @param {number} deltaTime - the time elapsed since the last update call 36 | */ 37 | update(deltaTime) { 38 | for (const stats of this.registry) { 39 | stats.frame++; 40 | stats.elapsedTime += deltaTime; 41 | if (stats.elapsedTime >= REFRESH_SEC) { 42 | // Note: We dont want to directly update the stats.textContent html element 43 | // as that will fill it in as an html value on the screen in 2D. We only 44 | // want to update the stats.textObj.text here directly for the 3D element 45 | // to update. 46 | stats.textObj.text = (stats.frame / stats.elapsedTime).toFixed(2) + ' fps'; 47 | 48 | stats.frame = 0; 49 | stats.elapsedTime = 0.0; 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/core/entities/MRButtonEntity.js: -------------------------------------------------------------------------------- 1 | import { MRTextEntity } from 'mrjs/core/entities/MRTextEntity'; 2 | 3 | /** 4 | * @class MRButtonEntity 5 | * @classdesc 3D representation of a Button mimicking the html version. `mr-button` 6 | * @augments MRTextEntity 7 | */ 8 | export class MRButtonEntity extends MRTextEntity { 9 | /** 10 | * @class 11 | * @description Constructor for the Button entity, does the default. 12 | */ 13 | constructor() { 14 | super(); 15 | this.background.castShadow = true; 16 | this.object3D.name = 'button'; 17 | this.ignoreStencil = true; 18 | } 19 | } 20 | 21 | customElements.get('mr-button') || customElements.define('mr-button', MRButtonEntity); 22 | -------------------------------------------------------------------------------- /src/core/entities/MRDivEntity.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | import { MREntity } from 'mrjs/core/MREntity'; 4 | 5 | import { mrjsUtils } from 'mrjs'; 6 | 7 | /** 8 | * @class MRDivEntity 9 | * @classdesc The MREntity that is used to solely describe UI Elements. Defaults as the html `mr-div` representation. `mr-div` 10 | * @augments MREntity 11 | */ 12 | export class MRDivEntity extends MREntity { 13 | /** 14 | * @class 15 | * @description Constructor sets up the defaults for the background mesh, scaling, and world relevant elements. 16 | */ 17 | constructor() { 18 | super(); 19 | this.worldScale = new THREE.Vector3(); 20 | this.halfExtents = new THREE.Vector3(); 21 | this.physics.type = 'ui'; 22 | 23 | const geometry = mrjsUtils.geometry.UIPlane(1, 1, [0], 18); 24 | const material = new THREE.MeshStandardMaterial({ 25 | color: 0xfff, 26 | roughness: 0.7, 27 | metalness: 0.0, 28 | side: THREE.DoubleSide, 29 | }); 30 | 31 | this.background = new THREE.Mesh(geometry, material); 32 | this.background.receiveShadow = true; 33 | this.background.renderOrder = 3; 34 | this.background.visible = false; 35 | this.background.name = 'background'; 36 | this.object3D.add(this.background); 37 | this.object3D.name = 'mrDivEntity'; 38 | 39 | // allow stenciling when needed by default for UI elements, but also allows 40 | // overriding when needed. 41 | this.ignoreStencil = false; 42 | } 43 | 44 | _storedWidth = -1; 45 | _storedHeight = -1; 46 | _storedBorderRadii = -1; 47 | 48 | /** 49 | * @function 50 | * @description Calculates the height of the Entity based on the viewing-client's shape. If in Mixed Reality, adjusts the value appropriately. 51 | * @returns {number} - the resolved height 52 | */ 53 | get height() { 54 | const rect = this.getBoundingClientRect(); 55 | 56 | if (mrjsUtils.xr.isPresenting) { 57 | return rect.height / mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION; 58 | } 59 | return (rect.height / global.appHeight) * global.viewPortHeight; 60 | } 61 | 62 | /** 63 | * @function 64 | * @description Calculates the width of the Entity based on the viewing-client's shape. If in Mixed Reality, adjusts the value appropriately. 65 | * @returns {number} - the resolved width 66 | */ 67 | get width() { 68 | const rect = this.getBoundingClientRect(); 69 | 70 | if (mrjsUtils.xr.isPresenting) { 71 | return rect.width / mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION; 72 | } 73 | return (rect.width / global.appWidth) * global.viewPortWidth; 74 | } 75 | 76 | /** 77 | * @function 78 | * @description Calculates the border radius of the img based on the img tag in the shadow root 79 | * @returns {number} - the resolved height 80 | */ 81 | get borderRadii() { 82 | return this.compStyle.borderRadius.split(' ').map((r) => mrjsUtils.css.domToThree(r)); 83 | } 84 | 85 | /** 86 | * @function 87 | * @description Adding an entity as a sub-object of this panel (for example an mr-model, button, etc). 88 | * @param {MREntity} entity - the entity to be added. 89 | */ 90 | add(entity) { 91 | // `this` must have `mr-panel` as its closest parent entity for threejs to handle positioning appropriately. 92 | let panel = this.closest('mr-panel'); 93 | if (panel && entity instanceof MRDivEntity) { 94 | panel.add(entity); 95 | } else { 96 | this.object3D.add(entity.object3D); 97 | } 98 | 99 | // slight bump needed to avoid overlapping, glitchy visuals. 100 | entity.object3D.position.z = this.object3D.position.z + 0.001; 101 | } 102 | 103 | /** 104 | * @function 105 | * @description Removing an entity as a sub-object of this panel (for example an mr-model, button, etc). 106 | * @param {MREntity} entity - the entity to be removed added. 107 | */ 108 | removeEntity(entity) { 109 | // `this` must have `mr-panel` as its closest parent entity for threejs to handle positioning appropriately. 110 | let panel = this.closest('mr-panel'); 111 | if (panel && entity instanceof MRDivEntity) { 112 | panel.removeEntity(entity); 113 | } else { 114 | this.object3D.remove(entity.object3D); 115 | } 116 | } 117 | 118 | /** 119 | * @function 120 | * @description (async) connects the background geometry of this item to an actual UIPlane geometry. 121 | */ 122 | async connected() { 123 | this.background.geometry = mrjsUtils.geometry.UIPlane(this.width, this.height, [0], 18); 124 | } 125 | } 126 | 127 | customElements.get('mr-div') || customElements.define('mr-div', MRDivEntity); 128 | -------------------------------------------------------------------------------- /src/core/entities/MRHyperlinkEntity.js: -------------------------------------------------------------------------------- 1 | import { MRTextEntity } from 'mrjs/core/entities/MRTextEntity'; 2 | 3 | /** 4 | * @class MRHyperlinkEntity 5 | * @classdesc 3D representation of a hyperlink. `mr-a` 6 | * @augments MRTextEntity 7 | */ 8 | export default class MRHyperlinkEntity extends MRTextEntity { 9 | /** 10 | * Constructor for the Model entity, does the default. 11 | */ 12 | constructor() { 13 | super(); 14 | this.background.castShadow = true; 15 | this.object3D.name = 'hyperlink'; 16 | } 17 | 18 | /** 19 | * @function 20 | * @description Creates the link object if it's not already created and handles the href and 21 | * target attributes. 22 | */ 23 | _createLink() { 24 | if (!this.link) { 25 | this.link = document.createElement('a'); 26 | this.link.setAttribute('href', this.getAttribute('href') ?? undefined); 27 | this.link.setAttribute('target', this.getAttribute('target') ?? undefined); 28 | } 29 | } 30 | 31 | /** 32 | * @function 33 | * @description Grabs the href of the link object 34 | * @returns {string} the href value 35 | */ 36 | get href() { 37 | this._createLink(); 38 | return this.link.getAttribute('href'); 39 | } 40 | 41 | /** 42 | * @function 43 | * @description Sets the href of the link object 44 | * @param {string} src_str - the new href value 45 | */ 46 | set href(src_str) { 47 | this._createLink(); 48 | this.link.setAttribute('href', src_str); 49 | } 50 | 51 | /** 52 | * @function 53 | * @description (async) makes sure the link object is created and sets up event 54 | * listeners for touchstart and click. 55 | */ 56 | async connected() { 57 | super.connected(); 58 | this._createLink(); 59 | 60 | this.addEventListener('touchstart', () => { 61 | this.classList.add('active'); 62 | }); 63 | 64 | this.addEventListener('click', () => { 65 | this.link.click(); 66 | }); 67 | } 68 | } 69 | 70 | customElements.get('mr-a') || customElements.define('mr-a', MRHyperlinkEntity); 71 | -------------------------------------------------------------------------------- /src/core/entities/MRImageEntity.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | import { MRMediaEntity } from 'mrjs/core/entities/MRMediaEntity'; 4 | 5 | import { mrjsUtils } from 'mrjs'; 6 | 7 | /** 8 | * @class MRImageEntity 9 | * @classdesc Base html image represented in 3D space. `mr-image` 10 | * @augments MRMediaEntity 11 | */ 12 | export class MRImageEntity extends MRMediaEntity { 13 | /** 14 | * @class 15 | * @description Constructs a base image entity using a UIPlane and other 3D elements as necessary. 16 | */ 17 | constructor() { 18 | super(); 19 | 20 | // object3D and rest of mrvideo is pre-created in MRMediaEntity 21 | this.object3D.name = 'image'; 22 | } 23 | 24 | /** 25 | * @function 26 | * @description Gets the width of the internal media object 27 | * @returns {number} width - the value of the width 28 | */ 29 | get mediaWidth() { 30 | return this.media.width; 31 | } 32 | 33 | /** 34 | * @function 35 | * @description Gets the height of the internal media object 36 | * @returns {number} height - the value of the height 37 | */ 38 | get mediaHeight() { 39 | return this.media.height; 40 | } 41 | 42 | /** 43 | * @function 44 | * @description (async) handles setting up this Image and associated 3D geometry style (from css) once it is connected to run as an entity component. 45 | */ 46 | async connected() { 47 | this.media = document.createElement('img'); 48 | await super.connected(); 49 | } 50 | 51 | /** 52 | * @function 53 | * @description Loads the Media texture of the setup this.media object based on its html source info. 54 | */ 55 | loadMediaTexture() { 56 | mrjsUtils.material 57 | .loadTextureAsync(this.media.src) 58 | .then((texture) => { 59 | this.texture = texture; 60 | this.object3D.material.map = texture; 61 | }) 62 | .catch((error) => { 63 | console.error('Error loading texture:', error); 64 | }); 65 | } 66 | 67 | /** 68 | * @function 69 | * @description Callback function of MREntity - Updates the image's cover,fill,etc based on the mutation request. 70 | * @param {object} mutation - the update/change/mutation to be handled. 71 | */ 72 | mutated(mutation) { 73 | // Mutations are only understood by their actual type. Any mutation 74 | // passed through MRMediaEntity directly is undefined since it is not 75 | // a direct element for users. So we do the if-check here and then 76 | // follow the same as the parent's functionality. 77 | if (mutation.type == 'attributes' && mutation.attributeName == 'src') { 78 | super.mutated(); 79 | } 80 | } 81 | } 82 | 83 | customElements.get('mr-img') || customElements.define('mr-img', MRImageEntity); 84 | -------------------------------------------------------------------------------- /src/core/entities/MRLightEntity.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | import { MREntity } from 'mrjs/core/MREntity'; 4 | 5 | /** 6 | * @class MRLightEntity 7 | * @classdesc Represents lights in 3D space. `mr-light` 8 | * @augments MREntity 9 | */ 10 | export class MRLightEntity extends MREntity { 11 | /** 12 | * @class 13 | * @description Constructs the base 3D object. 14 | */ 15 | constructor() { 16 | super(); 17 | this.object3D = new THREE.PointLight({}); 18 | this.object3D.name = 'pointLight'; 19 | } 20 | 21 | /** 22 | * @function 23 | * @description (async) handles setting up this Light once it is connected to run as an entity component. 24 | */ 25 | async connected() { 26 | this.object3D.color.setStyle(this.getAttribute('color')); 27 | this.object3D.intensity = parseFloat(this.getAttribute('intensity')) ?? 1; 28 | } 29 | 30 | /** 31 | * @function 32 | * @description (async) Updates the lights color and intensity as requested. 33 | * @param {object} mutation - the update/change/mutation to be handled. 34 | */ 35 | mutated = (mutation) => { 36 | if (mutation.type != 'attributes') { 37 | return; 38 | } 39 | switch (mutation.attributeName) { 40 | case 'color': 41 | // TODO - set via css 42 | this.object3D.color.setStyle(this.getAttribute('color')); 43 | break; 44 | 45 | case 'intensity': 46 | this.object3D.intensity = this.getAttribute('intensity'); 47 | break; 48 | 49 | default: 50 | break; 51 | } 52 | }; 53 | } 54 | 55 | customElements.get('mr-light') || customElements.define('mr-light', MRLightEntity); 56 | -------------------------------------------------------------------------------- /src/core/entities/MRSkyBoxEntity.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | import { MREntity } from 'mrjs/core/MREntity'; 4 | import { MRSystem } from 'mrjs/core/MRSystem'; 5 | 6 | /** 7 | * @class MRSkyBoxEntity 8 | * @classdesc The skybox entity that allows users to give multiple images to pattern into the 3D background space. `mr-skybox` 9 | * @augments MREntity 10 | */ 11 | export class MRSkyBoxEntity extends MREntity { 12 | /** 13 | * @class 14 | * @description Constructor for skybox - defaults to the usual impl of an Entity. 15 | */ 16 | constructor() { 17 | super(); 18 | this.object3D.name = 'skybox'; 19 | 20 | this.skybox = null; 21 | this.textureLoadedCallbacks = []; 22 | } 23 | 24 | /** 25 | * @function 26 | * @description Callback function triggered when the texture is successfully loaded. 27 | * It sets the loaded texture as the background and notifies all registered callbacks. 28 | * @param {THREE.Texture} texture - The loaded texture. 29 | */ 30 | onTextureLoaded(texture) { 31 | if (this.skybox) { 32 | if (Array.isArray(texture.images) && texture.images.length === 6) { 33 | // Handle cube texture case 34 | if (this.skybox.material !== undefined) { 35 | this.skybox.material.dispose(); 36 | } 37 | this.skybox.material = new THREE.MeshStandardMaterial({ 38 | envMap: texture, 39 | side: THREE.BackSide, // Render only on the inside 40 | }); 41 | } else { 42 | // Handle single texture case 43 | if (this.skybox.material !== undefined) { 44 | this.skybox.material.dispose(); 45 | } 46 | this.skybox.material = new THREE.MeshBasicMaterial({ 47 | map: texture, 48 | side: THREE.BackSide, // Render only on the inside 49 | opacity: 1, 50 | }); 51 | } 52 | } 53 | this.textureLoadedCallbacks.forEach((callback) => callback(texture)); 54 | } 55 | 56 | /** 57 | * @function 58 | * @description (async) Lifecycle method that is called when the entity is connected. 59 | * This method initializes and starts the texture loading process. 60 | */ 61 | async connected() { 62 | // you can have texturesList be all individual textures 63 | // or you can store them in a specified path and just 64 | // load them up solely by filename in that path. 65 | 66 | this.texturesList = mrjsUtils.html.resolvePath(this.getAttribute('src')); 67 | if (!this.texturesList) { 68 | return; 69 | } 70 | 71 | const textureNames = this.texturesList.split(','); 72 | const path = this.getAttribute('pathToTextures'); 73 | const textureUrls = textureNames.map((name) => mrjsUtils.html.resolvePath(path ? path + name : name)); 74 | 75 | let geometry; 76 | let textureLoader; 77 | if (textureNames.length > 1) { 78 | geometry = new THREE.BoxGeometry(900, 900, 900); 79 | textureLoader = new THREE.CubeTextureLoader(); 80 | textureLoader.load(textureUrls, this.onTextureLoaded.bind(this)); 81 | } else if (textureUrls.length == 1) { 82 | geometry = new THREE.SphereGeometry(900, 32, 16); 83 | textureLoader = new THREE.TextureLoader(); 84 | textureLoader.load(textureUrls[0], this.onTextureLoaded.bind(this)); 85 | } 86 | 87 | if (this.skybox) { 88 | // Remove existing skybox if present 89 | this.object3D.remove(this.skybox); 90 | this.skybox.dispose(); 91 | } 92 | this.skybox = new THREE.Mesh(geometry); // going to passively load texture on async 93 | this.object3D.add(this.skybox); 94 | } 95 | 96 | /** 97 | * @function 98 | * @description Set the opacity of the skybox itself. Useful for blending between the outside and MR. Also 99 | * useful for cases where you want to blend between different skybox versions. 100 | */ 101 | set setOpacity(val) { 102 | this.object3D.traverse((child) => { 103 | if (child.isMesh) { 104 | child.material.transparent = true; 105 | child.material.opacity = val; 106 | child.material.needsUpdate = true; 107 | } 108 | }); 109 | } 110 | 111 | /** 112 | * @function 113 | * @description On load event function - right now defaults to do nothing. 114 | */ 115 | onLoad = () => {}; 116 | } 117 | customElements.define('mr-skybox', MRSkyBoxEntity); 118 | -------------------------------------------------------------------------------- /src/core/entities/MRStatsEntity.js: -------------------------------------------------------------------------------- 1 | import { MRTextEntity } from 'mrjs/core/entities/MRTextEntity'; 2 | 3 | /** 4 | * @class MRStatsEntity 5 | * @classdesc The FPS counter entity. For simplicity, easy implementation, and good performance, 6 | * it is based on MRTextEntity and just shows the FPS counter number as text for now. 7 | * Ideally we want to improve later, like improving the visual quality and more info. 8 | * Note that stats entity that has a huge bad performance impact doesn't really make 9 | * sense so it should be kept simple and fast. 10 | * @augments MRTextEntity 11 | */ 12 | export class MRStatsEntity extends MRTextEntity { 13 | /** 14 | * @class 15 | * @description Constructor for the MRStatsEntity object. 16 | * Initializes some variables used to track and calculate the fps. 17 | */ 18 | constructor() { 19 | super(); 20 | this.frame = 0; 21 | this.elapsedTime = 0.0; 22 | this.object3D.name = 'stats'; 23 | } 24 | } 25 | 26 | customElements.get('mr-stats') || customElements.define('mr-stats', MRStatsEntity); 27 | -------------------------------------------------------------------------------- /src/core/entities/MRVideoEntity.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | import { MRMediaEntity } from 'mrjs/core/entities/MRMediaEntity'; 4 | 5 | import { mrjsUtils } from 'mrjs'; 6 | 7 | /** 8 | * @class MRVideoEntity 9 | * @classdesc Base html video represented in 3D space. `mr-video` 10 | * @augments MRMediaEntity 11 | */ 12 | export class MRVideoEntity extends MRMediaEntity { 13 | /** 14 | * @class 15 | * @description Constructs a base video entity using a UIPlane and other 3D elements as necessary. 16 | */ 17 | constructor() { 18 | super(); 19 | this.object3D.name = 'video'; 20 | this.playing = false; 21 | } 22 | 23 | /** 24 | * @function 25 | * @description Calculates the width of the video based on the video tag itself 26 | * @returns {number} - the resolved width 27 | */ 28 | get mediaWidth() { 29 | return this.media.videoWidth; 30 | } 31 | 32 | /** 33 | * @function 34 | * @description Calculates the height of the video based on the video tag itself 35 | * @returns {number} - the resolved height 36 | */ 37 | get mediaHeight() { 38 | return this.media.videoHeight; 39 | } 40 | 41 | /** 42 | * @function 43 | * @description Loads the associated video into 3D based on its html properties. 44 | */ 45 | loadMediaTexture() { 46 | mrjsUtils.material 47 | .loadVideoTextureAsync(this.media) 48 | .then((texture) => { 49 | this.texture = texture; 50 | this.object3D.material.map = texture; 51 | this.playing = true; // since we have videos auto play on silent to start 52 | }) 53 | .catch((error) => { 54 | console.error('Error loading texture:', error); 55 | }); 56 | } 57 | 58 | /** 59 | * @function 60 | * @description (async) handles setting up this video and associated 3D geometry style (from css) once it is connected to run as an entity component. 61 | */ 62 | async connected() { 63 | this.media = document.createElement('video'); 64 | this.media.setAttribute('crossorigin', 'anonymous'); 65 | 66 | await super.connected(); 67 | } 68 | 69 | /** 70 | * @function 71 | * @description Sets the srcObject of the video media (since it uses 'srcObject' instead of 'src' like other items). 72 | * @param {string} src - the string to the new source object we want 73 | */ 74 | set srcObject(src) { 75 | this.media.srcObject = src; 76 | // on loadeddata event, update the objectFitDimensions 77 | this.media.addEventListener('loadeddata', () => { 78 | this.computeObjectFitDimensions(); 79 | }); 80 | } 81 | 82 | /** 83 | * @function 84 | * @description Plays the video in the shadow root 85 | */ 86 | play() { 87 | this.media.play(); 88 | this.playing = true; 89 | } 90 | 91 | /** 92 | * @function 93 | * @description Pauses the video in the shadow root 94 | */ 95 | pause() { 96 | this.media.pause(); 97 | this.playing = false; 98 | } 99 | } 100 | 101 | customElements.get('mr-video') || customElements.define('mr-video', MRVideoEntity); 102 | -------------------------------------------------------------------------------- /src/core/entities/MRVolumeEntity.js: -------------------------------------------------------------------------------- 1 | import { MREntity } from 'mrjs/core/MREntity'; 2 | 3 | /** 4 | * @class MRVolumeEntity 5 | * @classdesc Representation of a visible region in 3D space. Models and other entities can move 6 | * throughout the space and leave the space, yet will only be rendered in the visual area of 7 | * the volume. From a conceptual perspective it is considered a ‘clipping volume’. 8 | * @augments MREntity 9 | */ 10 | export class MRVolumeEntity extends MREntity { 11 | /** 12 | * @class 13 | * @description Creates the volume as a base THREE.js object3D 14 | */ 15 | constructor() { 16 | super(); 17 | this.volume = new THREE.Object3D(); 18 | this.object3D.add(this.volume); 19 | } 20 | 21 | /** 22 | * @function 23 | * @description (async) handles creating clipping geometry around the entire volume for visible restrictions. 24 | */ 25 | async connected() { 26 | this.clipping = new MRClippingGeometry(new THREE.BoxGeometry(1, 1, 1)); 27 | this.ignoreStencil = true; 28 | 29 | this.addEventListener('anchored', () => { 30 | if (this.plane) { 31 | let height = this.plane.dimensions.x <= this.plane.dimensions.z ? this.plane.dimensions.x : this.plane.dimensions.z; 32 | this.volume.position.y += height / 2; 33 | this.clipping.geometry.copy(new THREE.BoxGeometry(this.plane.dimensions.x, height, this.plane.dimensions.z)); 34 | } 35 | }); 36 | } 37 | } 38 | 39 | customElements.get('mr-volume') || customElements.define('mr-volume', MRVolumeEntity); 40 | -------------------------------------------------------------------------------- /src/core/user/MRUser.js: -------------------------------------------------------------------------------- 1 | import { MRHand } from 'mrjs/core/user/MRHand'; 2 | 3 | /** 4 | * @class MRUser 5 | */ 6 | export default class MRUser { 7 | forward = new THREE.Object3D(); 8 | 9 | origin = new THREE.Object3D(); 10 | 11 | spotlight = null; 12 | 13 | hands = { 14 | left: null, 15 | right: null, 16 | }; 17 | /** 18 | * Constructor for the MRUser class, sets up the camera, hands, and spotlight information. 19 | * @param {object} camera - the threejs camera to be used as the user's pov. 20 | * @param {object} scene - the threejs scene in which the user will be immersed. 21 | */ 22 | constructor(camera, scene) { 23 | this.camera = camera; 24 | 25 | this.hands.left = new MRHand('left', scene); 26 | this.hands.right = new MRHand('right', scene); 27 | 28 | this.camera.add(this.forward); 29 | this.forward.position.setZ(-0.5); 30 | this.forward.position.setX(0.015); 31 | 32 | this.camera.add(this.origin); 33 | this.origin.position.setX(0.015); 34 | 35 | this.leftWorldPosition = new THREE.Vector3(); 36 | this.rightWorldPosition = new THREE.Vector3(); 37 | this.worldPosition = new THREE.Vector3(); 38 | 39 | this.leftDistance = 0; 40 | this.rightDistance = 0; 41 | 42 | this.spotLightScale = 1; 43 | } 44 | 45 | /** 46 | * Initializes the spotlight associated with the user's pov. 47 | * @returns {object} spotlight - the spotlight to be used. 48 | */ 49 | initSpotlight() { 50 | this.spotlight = new THREE.Mesh(new THREE.CircleGeometry(1.3, 64), new THREE.MeshBasicMaterial()); 51 | this.spotlight.material.colorWrite = false; 52 | this.spotlight.renderOrder = 2; 53 | this.spotlight.rotation.x = -Math.PI / 2; 54 | 55 | return this.spotlight; 56 | } 57 | 58 | /** 59 | * The update function for a user, its spotlight, and its hands. 60 | */ 61 | update() { 62 | this.hands.left.update(); 63 | this.hands.right.update(); 64 | 65 | if (this.spotlight) { 66 | this.worldPosition.setFromMatrixPosition(this.origin.matrixWorld); 67 | this.worldPosition.y = 0; 68 | 69 | if (this.hands.left.active) { 70 | this.hands.left.controller.getWorldPosition(this.leftWorldPosition); 71 | this.leftWorldPosition.y = 0; 72 | this.leftDistance = this.worldPosition.distanceTo(this.leftWorldPosition); 73 | } else { 74 | this.leftDistance = 0; 75 | } 76 | 77 | if (this.hands.right.active) { 78 | this.hands.right.controller.getWorldPosition(this.rightWorldPosition); 79 | this.rightWorldPosition.y = 0; 80 | this.rightDistance = this.worldPosition.distanceTo(this.rightWorldPosition); 81 | } else { 82 | this.rightDistance = 0; 83 | } 84 | 85 | this.spotLightScale = this.leftDistance > this.rightDistance ? this.leftDistance : this.rightDistance; 86 | 87 | if (this.spotLightScale > 0) { 88 | this.spotLightScale += 1; 89 | this.spotlight.scale.setScalar(this.spotLightScale); 90 | } 91 | 92 | this.spotlight.position.setFromMatrixPosition(this.origin.matrixWorld); 93 | this.spotlight.position.y = 0; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/dataTypes/MRClippingGeometry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class MRClippingGeometry 3 | * @classdesc Geometry used in the clipping plane step. Separated out for clarity in the calculations. 4 | */ 5 | export class MRClippingGeometry { 6 | planes = []; 7 | 8 | intersection = false; 9 | 10 | global = false; 11 | 12 | // NOTE: There is probably a better way to do this 13 | 14 | // TODO - separate this out so it's only needed within the Clipping Geometry System. This is not useful being separate from that 15 | // must do that before deletion 16 | 17 | /** 18 | * @class 19 | * @description Constructor for the clipping geometry class. Sets the internal geometry object to the geometry that is passed through. 20 | * @param {object} geometry - The geometry to be captured internally by `this.geometry`. 21 | */ 22 | constructor(geometry) { 23 | // Limits to one segment BoxGeometry instance like created with 24 | // "new BoxGeometry(width, height, depth);" for simplicity for now. 25 | 26 | // The geometry type limitation may not be immediately obvious to users of this module. 27 | // If unsupported geometry is passed, no errors may be raised, but the behavior may 28 | // become erratic, and such bugs can be difficult to investigate. This check is in 29 | // place to avoid such unnecessary effort. 30 | if (geometry.type !== 'BoxGeometry' || geometry.parameters?.widthSegments !== 1 || geometry.parameters?.heightSegments !== 1 || geometry.parameters?.depthSegments !== 1) { 31 | throw new Error('Unsupported Clipping geometry type.'); 32 | } 33 | 34 | this.geometry = geometry; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/dataTypes/MRPlane.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class MRPlane 3 | * @classdesc a name space representation of an MR Plane 4 | */ 5 | export class MRPlane { 6 | // The semantic label of the plane 7 | // possible values: wall, table, ceiling, floor 8 | label = null; 9 | 10 | // The orientation of the plane 11 | // possible values: horizontal, vertical 12 | orientation = null; 13 | 14 | // the physical dimensions of the plane 15 | dimensions = new THREE.Vector3(); 16 | 17 | // a flag indicating whether or not the plane is currently occupied by an anchored entity 18 | occupied = false; // Each plane can have one Entity anchored to it for simplicity. 19 | 20 | // the THREE.js Mesh representation 21 | mesh = null; 22 | 23 | // the RAPIER physics body 24 | body = null; 25 | } 26 | -------------------------------------------------------------------------------- /src/defaultStyle.css: -------------------------------------------------------------------------------- 1 | mr-app { 2 | display: block; 3 | height: 100vh; 4 | width: 100%; 5 | } 6 | 7 | .inXR { 8 | width: 100vw; 9 | } 10 | 11 | mr-app * { 12 | box-sizing: border-box; 13 | opacity: 0%; 14 | } 15 | 16 | mr-app > canvas { 17 | position:fixed; 18 | visibility:visible; 19 | opacity: 100%; 20 | z-index: 999; 21 | } 22 | 23 | mr-div, mr-button, mr-img, mr-video, mr-a, mr-text, mr-textarea, mr-textfield, mr-stats { 24 | display: inline-block; 25 | position: relative; 26 | z-index: inherit; 27 | opacity: 100%; 28 | } 29 | 30 | mr-text, mr-stats { 31 | line-height: 100%; 32 | font-size: 16px; 33 | } 34 | 35 | mr-textarea, mr-textfield { 36 | background-color: rgba(255, 255, 255, 0.75); 37 | font-size: 16px; 38 | border-radius: 1%; 39 | } 40 | 41 | mr-textfield { 42 | min-height: 2em; 43 | font-size: 100%; 44 | } 45 | 46 | mr-textarea { 47 | min-height: 7em; 48 | } 49 | 50 | mr-panel { 51 | background-color: #fff; 52 | border-radius: 2%; 53 | position: fixed; 54 | overflow: auto; 55 | height: 100vh; 56 | width: 100vw; 57 | } 58 | 59 | mr-img, mr-video { 60 | object-fit: contain; 61 | } 62 | 63 | mr-button { 64 | padding: 5px; 65 | text-align: center; 66 | vertical-align: middle; 67 | background-color: #8a8a8a; 68 | border-radius: 0.5%; 69 | /* animation: back 0.25s ease-out forwards; */ 70 | width: fit-content; 71 | } 72 | 73 | mr-button.hover { 74 | background-color: #333; 75 | z-index: 5; /* end */ 76 | /* animation: forward 0.25s ease-in forwards; */ 77 | } 78 | 79 | mr-a { 80 | color: darkblue; 81 | } 82 | 83 | mr-a.hover { 84 | color: blue; 85 | z-index: 5; 86 | } 87 | 88 | mr-a.active { 89 | color: purple; 90 | 91 | } 92 | 93 | /* these work differently from in XR than expected, laurent should look into it */ 94 | /* button animation! */ 95 | 96 | @keyframes forward { 97 | to { 98 | z-index: 5; /* end */ 99 | } 100 | } 101 | 102 | @keyframes back { 103 | to { 104 | z-index: 1; /* end */ 105 | } 106 | } 107 | 108 | 109 | body { 110 | margin: 0; /* unclear why we need this */ 111 | } 112 | -------------------------------------------------------------------------------- /src/extras/index.js: -------------------------------------------------------------------------------- 1 | // /** 2 | // * @module mrjsExtras 3 | // * @description mrjsExtras module acts as a one stop shop for all extras of mrjs. Ie things that are part of it for now but arent part of the main library. 4 | // * Items can be grabbed by importing as `import { class } from 'mrjs';` like any other class in mrjs. 5 | // */ 6 | 7 | // // TODO - this should auto grab instead of manually be updated as manual updates will create problems. 8 | 9 | import { Refractor } from './Refractor.js'; 10 | import { Water, WaterSystem } from './Water.js'; 11 | 12 | export { Refractor, Water, WaterSystem }; 13 | export { WaterRefractionShader } from 'three/examples/jsm/shaders/WaterRefractionShader.js'; 14 | -------------------------------------------------------------------------------- /src/global.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @global 3 | * @type {number} 4 | * @description the noted viewport height 5 | */ 6 | global.viewPortHeight = 0; 7 | 8 | /** 9 | * @global 10 | * @type {number} 11 | * @description the noted viewport width 12 | */ 13 | global.viewPortWidth = 0; 14 | 15 | /** 16 | * @global 17 | * @type {number} 18 | * @description UI needs to be scaled down in XR, 1:1 scale is huuuuge 19 | */ 20 | global.XRScale = 1 / 2; 21 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module mrjs 3 | * @description The one-stop-shop module for mrjs (including an import for mrjsUtils). Includes the ability to access threejs directly as well. 4 | */ 5 | 6 | // TODO - this should auto grab instead of manually be updated as manual updates will create problems. 7 | // for import and export 8 | 9 | import * as THREE from 'three'; 10 | 11 | // Log the version number 12 | import { version } from '../package.json'; 13 | console.log('-- ᴍʀjs Current Versioning --\nᴍʀjs :', version, '( using threejs :', THREE.REVISION, ')'); 14 | 15 | // STYLE 16 | import './defaultStyle.css'; 17 | 18 | // GLOBAL 19 | import './global'; 20 | 21 | // DATATYPES 22 | import './dataTypes/MRClippingGeometry'; 23 | import './dataTypes/MRPlane'; 24 | // DATAMANAGERS 25 | import './dataManagers/MRPlaneManager'; 26 | 27 | // CORE 28 | import './core/MRApp'; 29 | import './core/MRElement'; 30 | import './core/MREntity'; 31 | import './core/MRSystem'; 32 | // CORE - USER 33 | import './core/user/MRHand'; 34 | import './core/user/MRUser'; 35 | // CORE - ENTITIES 36 | import './core/entities/MRButtonEntity'; 37 | import './core/entities/MRDivEntity'; 38 | import './core/entities/MRHyperlinkEntity'; 39 | import './core/entities/MRImageEntity'; 40 | import './core/entities/MRLightEntity'; 41 | import './core/entities/MRMediaEntity'; 42 | import './core/entities/MRModelEntity'; 43 | import './core/entities/MRPanelEntity'; 44 | import './core/entities/MRSkyBoxEntity'; 45 | import './core/entities/MRStatsEntity'; 46 | import './core/entities/MRTextEntity'; 47 | import './core/entities/MRTextAreaEntity'; 48 | import './core/entities/MRTextFieldEntity'; 49 | import './core/entities/MRTextInputEntity'; 50 | import './core/entities/MRVideoEntity'; 51 | import './core/entities/MRVolumeEntity'; 52 | // CORE - COMPONENT-SYSTEMS 53 | import './core/componentSystems/AnchorSystem'; 54 | import './core/componentSystems/AnimationSystem'; 55 | import './core/componentSystems/BoundaryVisibilitySystem'; 56 | import './core/componentSystems/ClippingSystem'; 57 | import './core/componentSystems/ControlSystem'; 58 | import './core/componentSystems/GeometryStyleSystem'; 59 | import './core/componentSystems/InstancingSystem'; 60 | import './core/componentSystems/LayoutSystem'; 61 | import './core/componentSystems/MaskingSystem'; 62 | import './core/componentSystems/MaterialStyleSystem'; 63 | import './core/componentSystems/PhysicsSystem'; 64 | import './core/componentSystems/SkyBoxSystem'; 65 | import './core/componentSystems/StatsSystem'; 66 | import './core/componentSystems/TextSystem'; 67 | 68 | // EXPORTS 69 | 70 | // THREE - So users dont need a separate versioning import for it. 71 | export * as THREE from 'three'; 72 | 73 | // MRJS - Exporting only necessary items for users to overwrite as they use MRjs. 74 | export * from 'mrjs/core/MRSystem'; 75 | export * from 'mrjs/core/MREntity'; 76 | export * from 'mrjs/core/entities/MRButtonEntity'; 77 | export * from 'mrjs/core/entities/MRDivEntity'; 78 | export * from 'mrjs/core/entities/MRHyperlinkEntity'; 79 | export * from 'mrjs/core/entities/MRImageEntity'; 80 | export * from 'mrjs/core/entities/MRLightEntity'; 81 | export * from 'mrjs/core/entities/MRMediaEntity'; 82 | export * from 'mrjs/core/entities/MRModelEntity'; 83 | export * from 'mrjs/core/entities/MRPanelEntity'; 84 | export * from 'mrjs/core/entities/MRSkyBoxEntity'; 85 | export * from 'mrjs/core/entities/MRTextAreaEntity'; 86 | export * from 'mrjs/core/entities/MRTextEntity'; 87 | export * from 'mrjs/core/entities/MRTextFieldEntity'; 88 | export * from 'mrjs/core/entities/MRVideoEntity'; 89 | export * from 'mrjs/core/entities/MRVolumeEntity'; 90 | 91 | // EXTRAS 92 | export * from './extras/index.js'; 93 | 94 | // UTILS - exporting as a named group since it's a submodule of this js module 95 | export { mrjsUtils } from './utils/index.js'; 96 | -------------------------------------------------------------------------------- /src/utils/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace app 3 | * @description Useful namespace for helping with app utility functions. 4 | */ 5 | let app = { 6 | get scale() { 7 | if (mrjsUtils.xr.isPresenting) { 8 | return 0.5; 9 | } 10 | return 1; 11 | }, 12 | }; 13 | 14 | export { app }; 15 | -------------------------------------------------------------------------------- /src/utils/CSS.js: -------------------------------------------------------------------------------- 1 | import { warning } from 'mrjsUtils/Notify'; 2 | 3 | /** 4 | * @namespace css 5 | * @description Useful namespace for helping with CSS utility functions 6 | */ 7 | let css = {}; 8 | 9 | let rootStyle = undefined; 10 | css.getVarFromRoot = function (str) { 11 | if (!rootStyle) { 12 | const root = document.documentElement; 13 | rootStyle = getComputedStyle(root); 14 | if (!rootStyle) { 15 | mrjsUtils.warning.warn('Bad var value. Tracked as css-variable, but no `:root` setup found:', str); 16 | return; 17 | } 18 | } 19 | 20 | return rootStyle.getPropertyValue(str).trim(); 21 | }; 22 | 23 | css.extractNumFromPixelStr = function (str) { 24 | const result = str.match(/(\d+)px/); 25 | return result ? parseInt(result[1]) : null; 26 | }; 27 | 28 | /** 29 | * @function 30 | * @description Converts the dom string to a 3D numerical value 31 | * @param {string} val - the dom css information includes items of the form `XXXpx`, `XXX%`, etc 32 | * @returns {number} - the 3D numerical represenation of the dom css value 33 | */ 34 | css.domToThree = function (val) { 35 | if (typeof val === 'string') { 36 | const valuepair = val.split(/(\d+(?:\.\d+)?)/).filter(Boolean); 37 | if (valuepair.length > 1) { 38 | switch (valuepair[1]) { 39 | case 'px': 40 | if (mrjsUtils.xr.isPresenting) { 41 | return val.split('px')[0] / global.appWidth; 42 | } 43 | return (val.split('px')[0] / global.appWidth) * global.viewPortWidth; 44 | case '%': 45 | if (mrjsUtils.xr.isPresenting) { 46 | return parseFloat(val) / 100; 47 | } 48 | return (parseFloat(val) / 100) * global.viewPortWidth; 49 | default: 50 | return val; 51 | } 52 | } 53 | } 54 | return val; 55 | }; 56 | 57 | /** 58 | * @function 59 | * @memberof css 60 | * @description Converts 3D world positions to display positions based on global viewPort information. 61 | * Useful as part of the layout system and css value handling (px<-->threejs). 62 | * @param {number} val - the 3D value to be converted to 2D pixel space 63 | * @returns {number} - the 2D pixel space representation of value. 64 | */ 65 | css.threeToPx = function (val) { 66 | return (val / global.viewPortHeight) * global.appHeight; 67 | }; 68 | 69 | /** 70 | * @function 71 | * @memberof css 72 | * @description Converts display positions to 3D world positions to based on global viewPort information. 73 | * Useful as part of the layout system and css value handling (px<-->threejs). 74 | * @param {number} val - the 2D pixel space value to be converted to 3D space. 75 | * @returns {number} - the 3D representation of value. 76 | */ 77 | css.pxToThree = function (val) { 78 | let px = val instanceof String ? val.split('px')[0] : val; 79 | 80 | if (mrjsUtils.xr.isPresenting) { 81 | return px / global.appWidth; 82 | } 83 | return (px / global.appWidth) * global.viewPortWidth; 84 | }; 85 | 86 | export { css }; 87 | -------------------------------------------------------------------------------- /src/utils/Color.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace color 3 | * @description Useful namespace for helping with color utility functions 4 | */ 5 | let color = {}; 6 | 7 | /** 8 | * @function 9 | * @memberof color 10 | * @param {string} hex - the hex code including "#" at the beginning 11 | * @description Converts a hex code into a usable rgba object value 12 | * @returns {object} - the calculated rgba value representation of the hex code 13 | * { 14 | * r: number, // Red component (0-255) 15 | * g: number, // Green component (0-255) 16 | * b: number, // Blue component (0-255) 17 | * a: number // Alpha component (0-1 for transparency) 18 | * } 19 | */ 20 | color.hexToRgba = function (hex) { 21 | let r = 0, 22 | g = 0, 23 | b = 0, 24 | a = 1; // Default is black 25 | if (hex.startsWith('#')) { 26 | hex = hex.substring(1); 27 | } 28 | 29 | if (hex.length === 3) { 30 | r = parseInt(hex[0] + hex[0], 16); 31 | g = parseInt(hex[1] + hex[1], 16); 32 | b = parseInt(hex[2] + hex[2], 16); 33 | } else if (hex.length === 6) { 34 | r = parseInt(hex.substring(0, 2), 16); 35 | g = parseInt(hex.substring(2, 4), 16); 36 | b = parseInt(hex.substring(4, 6), 16); 37 | } else if (hex.length === 4) { 38 | r = parseInt(hex[0] + hex[0], 16); 39 | g = parseInt(hex[1] + hex[1], 16); 40 | b = parseInt(hex[2] + hex[2], 16); 41 | a = parseInt(hex[3] + hex[3], 16) / 255; 42 | } else if (hex.length === 8) { 43 | r = parseInt(hex.substring(0, 2), 16); 44 | g = parseInt(hex.substring(2, 4), 16); 45 | b = parseInt(hex.substring(4, 6), 16); 46 | a = parseInt(hex.substring(6, 8), 16) / 255; 47 | } 48 | return { r, g, b, a }; 49 | }; 50 | 51 | color.setObject3DColor = function (object3D, new_color, compStyle_opacity = '1', default_color = '#000') { 52 | const setColor = (object3D, new_color, compStyle_opacity, default_color) => { 53 | if (new_color.startsWith('rgba')) { 54 | const rgba = new_color 55 | .match(/rgba?\(([^)]+)\)/)[1] 56 | .split(',') 57 | .map((n) => parseFloat(n.trim())); 58 | object3D.material.color.setStyle(`rgb(${rgba[0]}, ${rgba[1]}, ${rgba[2]})`); 59 | object3D.material.transparent = rgba.length === 4 && rgba[3] < 1; 60 | object3D.material.opacity = rgba.length === 4 ? rgba[3] : 1; 61 | object3D.visible = !(rgba.length === 4 && rgba[3] === 0); 62 | } else if (new_color.startsWith('rgb')) { 63 | // RGB colors are treated as fully opaque 64 | object3D.material.color.setStyle(new_color); 65 | object3D.material.transparent = false; 66 | object3D.visible = true; 67 | } else if (new_color.startsWith('#')) { 68 | const { r, g, b, a } = mrjsUtils.color.hexToRgba(new_color); 69 | object3D.material.color.setStyle(`rgb(${r}, ${g}, ${b})`); 70 | object3D.material.transparent = a < 1; 71 | object3D.material.opacity = a; 72 | object3D.visible = a !== 0; 73 | } else { 74 | // This assumes the color is a CSS color word or another valid CSS color value 75 | object3D.material.color.setStyle(new_color ?? default_color); 76 | object3D.material.transparent = false; 77 | object3D.visible = true; 78 | } 79 | if (compStyle_opacity < 1) { 80 | object3D.material.opacity = compStyle_opacity; 81 | } 82 | object3D.material.needsUpdate = true; 83 | }; 84 | 85 | if (object3D.isGroup) { 86 | mrjsUtils.warn.warn("setObject3DColor will not handle groups as expected, please use 'setEntityColor' instead."); 87 | } else { 88 | setColor(object3D, new_color, compStyle_opacity, default_color); 89 | } 90 | }; 91 | 92 | color.setEntityOpacity = function (object3D, compStyle_opacity) { 93 | entity.traverseObjects((object) => { 94 | if (object.isMesh) { 95 | mrjsUtils.color.setObject3DOpacity(object, compStyle_opacity); 96 | } 97 | }); 98 | }; 99 | 100 | color.setObject3DOpacity = function (object3D, compStyle_opacity) { 101 | const setOpacity = (object3D, compStyle_opacity) => { 102 | if (compStyle_opacity <= 1) { 103 | object3D.material.opacity = compStyle_opacity; 104 | } 105 | object3D.material.needsUpdate = true; 106 | }; 107 | 108 | if (object3D.isGroup) { 109 | mrjsUtils.warn.warn("setObject3DOpacity will not handle groups as expected, please use 'setEntityOpacity' instead."); 110 | } else { 111 | setOpacity(object3D, compStyle_opacity); 112 | } 113 | }; 114 | 115 | color.setEntityColor = function (entity, new_color, compStyle_opacity = '1', default_color = '#000') { 116 | entity.traverseObjects((object) => { 117 | if (object.isMesh) { 118 | mrjsUtils.color.setObject3DColor(object, new_color, compStyle_opacity, default_color); 119 | } 120 | }); 121 | }; 122 | 123 | export { color }; 124 | -------------------------------------------------------------------------------- /src/utils/Display.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace display 3 | * @description Useful namespace for helping with Display utility functions 4 | */ 5 | let display = {}; 6 | 7 | // const _VIRTUAL_DISPLAY_RESOLUTION = 1080; 8 | /** 9 | * @memberof display 10 | * @description Defaults to 1080; 11 | */ 12 | display.VIRTUAL_DISPLAY_RESOLUTION = 1080; //alert(_VIRTUAL_DISPLAY_RESOLUTION); 13 | 14 | /** 15 | * @function 16 | * @memberof display 17 | * @description Checks whether the user is on mobile or not based on a large list of potential options. 18 | * @returns {boolean} - returns true if on any mobile devices. 19 | */ 20 | display.mobileCheckFunction = function () { 21 | let userAgent = navigator.userAgent || navigator.vendor || window.opera; 22 | const userAgentRegex0 = new RegExp( 23 | '(android|bbd+|meego).+mobile|avantgo|bada/|blackberry|blazer|\ 24 | compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |\ 25 | maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|\ 26 | phone|p(ixi|re)/|plucker|pocket|psp|series(4|6)0|symbian|treo|\ 27 | up.(browser|link)|vodafone|wap|windows ce|xda|xiino', 28 | 'is' 29 | ); 30 | const userAgentRegex1 = new RegExp( 31 | '/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|\ 32 | ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|\ 33 | attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|\ 34 | bumb|bw-(n|u)|c55/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|\ 35 | craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|\ 36 | el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|\ 37 | g1 u|g560|gene|gf-5|g-mo|go(.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|\ 38 | hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|\ 39 | i-(20|go|ma)|i230|iac( |-|/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|\ 40 | iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |/)|klon|kpt |kwc-|\ 41 | kyo(c|k)|le(no|xi)|lg( g|/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|\ 42 | m3ga|m50/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|\ 43 | mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|\ 44 | n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|\ 45 | nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|\ 46 | phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|\ 47 | qtek|r380|r600|raks|rim9|ro(ve|zo)|s55/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|\ 48 | sdk/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|\ 49 | so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|\ 50 | tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|\ 51 | up(.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|\ 52 | voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|\ 53 | wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-', 54 | 'is' 55 | ); 56 | return userAgentRegex0.test(userAgent) || userAgentRegex1.test(userAgent.substr(0, 4)); 57 | }; 58 | 59 | export { display }; 60 | -------------------------------------------------------------------------------- /src/utils/Geometry.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | /** 4 | * @namespace geometry 5 | * @description Useful namespace for helping with geometry utility functions 6 | */ 7 | let geometry = {}; 8 | 9 | /** 10 | * @function 11 | * @memberof geometry 12 | * @description This construction function creates the UIPlane that is used as the backdrop for most mrjs Panel divs. 13 | * @param {number} width - the expected width of the plane. 14 | * @param {number} height - the expected height of the plane. 15 | * @param {number} radius_corner - the expected radius value to curve the planes corners. 16 | * @param {number} smoothness - the expected smoothness value. 17 | * @returns {THREE.BufferGeometry} - The completed threejs plane object. 18 | */ 19 | geometry.UIPlane = function (width, height, radius_corner, smoothness) { 20 | let w = width == 'auto' ? 1 : width; 21 | w = w != 0 ? w : 1; 22 | let h = height == 'auto' ? 1 : height; 23 | h = h != 0 ? h : 1; 24 | const r = radius_corner[0] == 0 ? 0.0001 : radius_corner[0]; 25 | const s = smoothness; // shortening for calculation quickness. 26 | 27 | if (!w || !h || !r || !s) { 28 | return null; 29 | } 30 | 31 | // helper const's 32 | const wi = w / 2 - r; // inner width 33 | const hi = h / 2 - r; // inner height 34 | const ul = r / w; // u left 35 | const ur = (w - r) / w; // u right 36 | const vl = r / h; // v low 37 | const vh = (h - r) / h; // v high 38 | 39 | const positions = [wi, hi, 0, -wi, hi, 0, -wi, -hi, 0, wi, -hi, 0]; 40 | 41 | const uvs = [ur, vh, ul, vh, ul, vl, ur, vl]; 42 | 43 | const n = [3 * (s + 1) + 3, 3 * (s + 1) + 4, s + 4, s + 5, 2 * (s + 1) + 4, 2, 1, 2 * (s + 1) + 3, 3, 4 * (s + 1) + 3, 4, 0]; 44 | 45 | const indices = [n[0], n[1], n[2], n[0], n[2], n[3], n[4], n[5], n[6], n[4], n[6], n[7], n[8], n[9], n[10], n[8], n[10], n[11]]; 46 | 47 | let phi; 48 | let cos; 49 | let sin; 50 | let xc; 51 | let yc; 52 | let uc; 53 | let vc; 54 | let idx; 55 | 56 | for (let i = 0; i < 4; i++) { 57 | xc = i < 1 || i > 2 ? wi : -wi; 58 | yc = i < 2 ? hi : -hi; 59 | 60 | uc = i < 1 || i > 2 ? ur : ul; 61 | vc = i < 2 ? vh : vl; 62 | 63 | for (let j = 0; j <= s; j++) { 64 | phi = (Math.PI / 2) * (i + j / s); 65 | cos = Math.cos(phi); 66 | sin = Math.sin(phi); 67 | 68 | positions.push(xc + r * cos, yc + r * sin, 0); 69 | 70 | uvs.push(uc + ul * cos, vc + vl * sin); 71 | 72 | if (j < s) { 73 | idx = (s + 1) * i + j + 4; 74 | indices.push(i, idx, idx + 1); 75 | } 76 | } 77 | } 78 | 79 | const geo = new THREE.BufferGeometry(); 80 | geo.setIndex(new THREE.BufferAttribute(new Uint32Array(indices), 1)); 81 | geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3)); 82 | geo.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvs), 2)); 83 | geo.computeBoundingBox(); 84 | geo.computeVertexNormals(); 85 | 86 | geo.name = 'uiPlane'; 87 | 88 | return geo; 89 | }; 90 | 91 | export { geometry }; 92 | -------------------------------------------------------------------------------- /src/utils/HTML.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace html 3 | * @description Useful namespace for helping with html utility functions 4 | */ 5 | let html = {}; 6 | 7 | /** 8 | * @function 9 | * @memberof html 10 | * @param {string} path - either a relative or full path inputted to an element. This can also be a path that has items separated by ',' so 11 | * that you can resolve multiple items at once, since we allow users to send us multiple files that way. 12 | * @param {string} baseUrl - a separate entry for if you want your url to start differently. this defaults to your window.location.origin. 13 | * Additionally removes all queries from the end of the url, leaving the input as just the origin and its pathname. 14 | * For ex: 'https://example.com/images/photo.png?version=2' becomes 'https://example.com/images/photo.png' 15 | * @description Given the path returns an absolute path resolved so relative linking works as expected. 16 | * @returns {string} a.href - the absolute path (or paths) 17 | */ 18 | html.resolvePath = function (path, baseUrl = window.location.origin) { 19 | const fixPath = (path, baseUrl) => { 20 | let a = document.createElement('a'); 21 | a.href = html.removeUrlQueries(path, baseUrl); 22 | return a.href; 23 | }; 24 | 25 | // Handle multiple paths separated by commas 26 | if (path.includes(',')) { 27 | return path 28 | .split(',') 29 | .map((p) => fixPath(p.trim())) 30 | .join(','); 31 | } 32 | 33 | // singular path 34 | return fixPath(path, baseUrl); 35 | }; 36 | 37 | /** 38 | * @function 39 | * @memberof html 40 | * @param {string} path - either a relative or full path inputted to an element. 41 | * @param {string} baseUrl - a separate entry for if you want your url to start differently. this defaults to your window.location.origin. 42 | * @description Removes all queries from the end of the url, leaving the input as just the origin and its pathname. 43 | * For ex: 'https://example.com/images/photo.png?version=2' becomes 'https://example.com/images/photo.png' 44 | * @returns {string} a.href - the absolute path 45 | */ 46 | html.removeUrlQueries = function (path, baseUrl = window.location.origin) { 47 | try { 48 | // Check if path is absolute. If not, use baseUrl as the second parameter 49 | let urlObj = new URL(path, baseUrl); 50 | return urlObj.origin + urlObj.pathname; 51 | } catch (error) { 52 | console.warn('Error processing URL:', error.message); 53 | return path; // Return the original path if there's an error 54 | } 55 | }; 56 | 57 | export { html }; 58 | -------------------------------------------------------------------------------- /src/utils/JS.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | /** 4 | * @namespace js 5 | * @description Useful namespace for helping with common needed JS quick functions 6 | */ 7 | let js = {}; 8 | 9 | /** 10 | * @function 11 | * @memberof js 12 | * @param {object} instance - the object whose class is being checked 13 | * @param {object} BaseClass - the given name of the BaseClass being checked against. Not in quotes. 14 | * @example JS.isInstanceOfBaseClassOnly(entity, MRDivEntity) would return true only on entities. 15 | * @description Given the parent, grabs either the parent's direct material or (in the case of a group) the 16 | * material of the first child hit. 17 | * @returns {object} material - the grabbed material 18 | */ 19 | js.isInstanceOfBaseClassOnly = function (instance, BaseClass) { 20 | return instance.constructor === BaseClass; 21 | }; 22 | 23 | js.applyAttributes = function (object, attribMap) { 24 | Object.entries(attributeMap).forEach(([key, value]) => { 25 | if (key in object) { 26 | object[key] = value; 27 | } 28 | }); 29 | }; 30 | 31 | js.isVariableDeclared = function (myVar) { 32 | return typeof myVar !== 'undefined'; 33 | }; 34 | 35 | export { js }; 36 | -------------------------------------------------------------------------------- /src/utils/Material.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { html } from 'mrjsUtils/HTML'; 3 | 4 | /** 5 | * @namespace material 6 | * @description Useful namespace for helping with Materials and threejs utility functions 7 | */ 8 | let material = {}; 9 | 10 | /** 11 | * @function 12 | * @memberof material 13 | * @param {object} parent - either a THREE.Group or a THREE.mesh/object 14 | * @description Given the parent, grabs either the parent's direct material or (in the case of a group) the 15 | * material of the first child hit. 16 | * @returns {object} material - the grabbed material 17 | */ 18 | material.getObjectMaterial = function (parent) { 19 | let foundMesh = false; 20 | let material; 21 | 22 | if (parent instanceof THREE.Group) { 23 | parent.traverse((child) => { 24 | if (!foundMesh && child instanceof THREE.Mesh) { 25 | material = child.material; 26 | foundMesh = true; 27 | } 28 | }); 29 | } else { 30 | material = parent.material; 31 | } 32 | 33 | return material; 34 | }; 35 | 36 | /** 37 | * @function 38 | * @memberof material 39 | * @param {object} parent - either a THREE.Group or a THREE.mesh/object 40 | * @param {object} material - a threejs material to be set for either the parent's direct material or 41 | * (in the case of a group) the material of all children within the parent group. 42 | * @description Given the parent, grabs either the parents direct material or (in the case of a group) the 43 | * material of the first child hit. 44 | * @returns {object} parent - the updated parent object 45 | */ 46 | material.setObjectMaterial = function (parent, material) { 47 | if (parent instanceof THREE.Group) { 48 | parent.traverse((child) => { 49 | if (child instanceof THREE.Mesh) { 50 | child.material = material; 51 | child.material.needsUpdate = true; 52 | } 53 | }); 54 | } else { 55 | parent.material = material; 56 | parent.material.needsUpdate = true; 57 | } 58 | return parent; 59 | }; 60 | 61 | /** 62 | * @function 63 | * @memberof material 64 | * @param {object} src - the url path to the data to be loaded 65 | * @description Function to load the texture asynchronously and return a promise 66 | * @returns {object} texture - the fully loaded texture 67 | */ 68 | material.loadTextureAsync = function (src) { 69 | return new Promise((resolve, reject) => { 70 | const textureLoader = new THREE.TextureLoader(); 71 | 72 | let resolvedSrc = html.resolvePath(src); 73 | 74 | // Use the img's src to load the texture 75 | textureLoader.load( 76 | resolvedSrc, 77 | (texture) => { 78 | resolve(texture); 79 | }, 80 | undefined, 81 | (error) => { 82 | reject(error); 83 | } 84 | ); 85 | }); 86 | }; 87 | 88 | /** 89 | * @function 90 | * @memberof material 91 | * @param {object} video - the html video element whose src contains the path to the data to be loaded 92 | * @description Function to load the texture asynchronously and return a promise 93 | * @returns {object} texture - the fully loaded texture 94 | */ 95 | material.loadVideoTextureAsync = function (video) { 96 | video.src = html.resolvePath(video.src); 97 | 98 | video.muted = true; // Mute the video to allow autoplay 99 | video.autoplay = false; //true; // Attempt to autoplay 100 | 101 | return new Promise((resolve, reject) => { 102 | // Event listener to ensure video is ready 103 | video.onloadeddata = () => { 104 | const videoTexture = new THREE.VideoTexture(video); 105 | videoTexture.needsUpdate = true; // Ensure the texture updates when the video plays 106 | 107 | video 108 | .play() 109 | .then(() => { 110 | console.log('Video playback started'); 111 | resolve(videoTexture); 112 | }) 113 | .catch((e) => { 114 | console.error('Error trying to play the video:', e); 115 | reject(e); 116 | }); 117 | }; 118 | 119 | video.onerror = (error) => { 120 | reject(new Error('Error loading video: ' + error.message)); 121 | }; 122 | 123 | // This can help with ensuring the video loads in some cases 124 | video.load(); 125 | }); 126 | }; 127 | 128 | export { material }; 129 | -------------------------------------------------------------------------------- /src/utils/Math.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | /** 4 | * @namespace math 5 | * @description Useful namespace for helping with Math utility functions including numerical, 3d, etc. 6 | */ 7 | let math = {}; 8 | 9 | /**************************/ 10 | /***** NUMERICAL MATH *****/ 11 | /**************************/ 12 | 13 | /** 14 | * @function 15 | * @memberof math 16 | * @description Rounds the inputted val to the nearest decimal place as denoted by the decimal parameter. 17 | * @example For example: roundTo(832.456, 10) = 832.4; roundTo(832.456, 1000) = 832.456; roundTo(832.456, 0.01) = 800; 18 | * @param {number} val - The number to be rounded. 19 | * @param {number} decimal - The decimal place targeted in the rounding. 20 | * @returns {number} - The rounded number to the requested decimal amount. 21 | */ 22 | math.roundTo = function (val, decimal) { 23 | return Math.round(val * decimal) / decimal; 24 | }; 25 | 26 | /** 27 | * @function 28 | * @memberof math 29 | * @description Rounds the inputted vector to the nearest decimal place as denoted by the decimal parameter. 30 | * @example For example: roundTo(<832.456, 92.10003, 23452.1>, 10) = <832.4, 92.1, 2342.1>; 31 | * @param {vector} vector - The vector of numbers to be rounded. 32 | * @param {number} decimal - The decimal place targeted in the rounding. 33 | */ 34 | math.roundVectorTo = function (vector, decimal) { 35 | vector.multiplyScalar(decimal); 36 | vector.roundToZero(); 37 | vector.divideScalar(decimal); 38 | }; 39 | 40 | /** 41 | * @function 42 | * @memberof math 43 | * @description Performs the radian To Degree calculation commonly used in math. 44 | * https://en.wikipedia.org/wiki/Degree_(angle) https://en.wikipedia.org/wiki/Radian 45 | * @param {number} val - The number to be converted from radians to degrees 46 | * @returns {number} - the calculated degree representation of val. 47 | */ 48 | math.radToDeg = function (val) { 49 | return (val * Math.PI) / 180; 50 | }; 51 | 52 | /*******************/ 53 | /***** 3D MATH *****/ 54 | /*******************/ 55 | 56 | /** 57 | * @function 58 | * @memberof math 59 | * @description Computes the bounding sphere of an inputted three group object. 60 | * @param {THREE.group} group - the group to be enclosed in the bounding sphere. 61 | * @param {THREE.group} relativeTo - object that the group is relative to. For example if the group is an apple held in a 62 | * character's hand, relativeTo would be the characters hand. When left as null, the bounding sphere defaults to the inputted groups original world matrix. 63 | * @returns {THREE.Sphere} - the resolved bounding sphere 64 | */ 65 | math.computeBoundingSphere = function (group, relativeTo = null) { 66 | let sphere = new THREE.Sphere(); 67 | let box = new THREE.Box3(); 68 | 69 | box.setFromObject(group); 70 | box.getBoundingSphere(sphere); 71 | 72 | sphere.applyMatrix4(relativeTo ? relativeTo.matrixWorld : group.matrixWorld); 73 | 74 | return sphere; 75 | }; 76 | 77 | export { math }; 78 | -------------------------------------------------------------------------------- /src/utils/Notify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace error 3 | * @description Useful namespace for helping with error utility functions 4 | */ 5 | let error = {}; 6 | 7 | /** 8 | * @function 9 | * @memberof error 10 | * @description Function helper to error in console if a child class is expected to overwrite a parent 11 | * class's function but didnt. 12 | */ 13 | error.emptyParentFunction = function () { 14 | console.error('Empty parent function was reached; this must be overridden in children.'); 15 | }; 16 | 17 | /** 18 | * @function 19 | * @param {string} string - string of texted emitted through the console. 20 | * @memberof error 21 | * @description Function helper separated out to console error for when we eventually have a more robust 22 | * erroring system. 23 | */ 24 | error.err = function (string) { 25 | console.error(string); 26 | }; 27 | 28 | /** 29 | * @namespace warn 30 | * @description Useful namespace for helping with error utility functions 31 | */ 32 | let warn = {}; 33 | 34 | /** 35 | * @function 36 | * @memberof warn 37 | * @description Function helper to warn in console if a child class might want to overwrite a parent 38 | * class's function but didnt. Useful for base classes that are more abstract classes (if in Java or C++) 39 | * to remind the user of the child class that there is more to implement. 40 | */ 41 | warn.EmptyParentFunction = function () { 42 | console.warn('Empty parent function was reached, make sure this was overridden in children if more execution was expected.'); 43 | }; 44 | 45 | /** 46 | * @function 47 | * @param {string} string - string of texted emitted through the console. 48 | * @memberof warn 49 | * @description Function helper separated out to console warn for when we eventually have a more robust 50 | * warning system. 51 | */ 52 | warn.warn = function (string) { 53 | console.warn(string); 54 | }; 55 | 56 | export { error, warn }; 57 | -------------------------------------------------------------------------------- /src/utils/Physics.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace physics 3 | * @description Useful namespace for helping with physics utility functions 4 | */ 5 | let physics = {}; 6 | 7 | /** 8 | * @memberof physics 9 | * @description the Rapier collision groups used throughout MRjs 10 | */ 11 | physics.CollisionGroups = { 12 | USER: 0x00020001, 13 | PLANES: 0x0004ffff, 14 | UI: 0x00010002, 15 | }; 16 | 17 | /** 18 | * @memberof physics 19 | * @description the RAPIER physics controller object 20 | */ 21 | physics.RAPIER = null; 22 | 23 | physics.initialized = false; 24 | 25 | physics.initializePhysics = async function () { 26 | if (!physics.initialized) { 27 | physics.RAPIER = await import('@dimforge/rapier3d'); 28 | physics.initialized = true; 29 | physics.eventQueue = new physics.RAPIER.EventQueue(true); 30 | physics.world = new physics.RAPIER.World({ x: 0.0, y: -9.81, z: 0.0 }); 31 | 32 | document.dispatchEvent(new CustomEvent('engine-started', { bubbles: true })); 33 | } 34 | return physics; 35 | }; 36 | 37 | // const _INPUT_COLLIDER_HANDLE_NAMES = {}; 38 | /** 39 | * @memberof physics 40 | * @description the Rapier INPUT_COLLIDER_HANDLE_NAMES 41 | */ 42 | physics.INPUT_COLLIDER_HANDLE_NAMES = {}; //alert(_INPUT_COLLIDER_HANDLE_NAMES); 43 | 44 | // const _COLLIDER_ENTITY_MAP = {}; 45 | /** 46 | * @memberof physics 47 | * @description the Rapier COLLIDER_ENTITY_MAP 48 | */ 49 | physics.COLLIDER_ENTITY_MAP = {}; 50 | 51 | export { physics }; 52 | -------------------------------------------------------------------------------- /src/utils/String.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace string 3 | * @description Useful namespace for helping with String utility functions 4 | */ 5 | let string = {}; 6 | 7 | /*******************************************************/ 8 | /************ JSON // String interactions **************/ 9 | /*******************************************************/ 10 | 11 | /** 12 | * @function 13 | * @memberof string 14 | * @description Converts and formats the inputted string to a json object. 15 | * @param {string} attrString - the string to be formatted 16 | * @returns {object} - object in json form 17 | */ 18 | string.stringToJson = function (attrString) { 19 | if (attrString == null) { 20 | return null; 21 | } 22 | const regexPattern = /(\w+):\s*([^;]+)/g; 23 | const jsonObject = {}; 24 | 25 | let match; 26 | while ((match = regexPattern.exec(attrString)) !== null) { 27 | const key = match[1].trim(); 28 | let value = match[2].trim(); 29 | 30 | // Check value type and convert if necessary 31 | if (value.includes(' ')) { 32 | value = value.split(' ').map((v) => parseFloat(v)); 33 | } else if (/^\d+(\.\d+)?$/.test(value)) { 34 | value = parseFloat(value); 35 | } else if (value === 'true') { 36 | value = true; 37 | } else if (value === 'false') { 38 | value = false; 39 | } 40 | 41 | jsonObject[key] = value; 42 | } 43 | 44 | return jsonObject; 45 | }; 46 | 47 | /** 48 | * @function 49 | * @memberof string 50 | * @description Converts and formats the inputted json object into a string. 51 | * @param {object} componentData - the json object to be formatted into a string 52 | * @returns {string} - the string representation of the json object 53 | */ 54 | string.jsonToString = function (componentData) { 55 | let compString = ''; 56 | 57 | for (const [key, value] of Object.entries(componentData)) { 58 | let stringValue; 59 | 60 | if (Array.isArray(value)) { 61 | // Convert array of numbers to space-separated string 62 | stringValue = value.join(' '); 63 | } else { 64 | // Use the value directly for numbers and booleans 65 | stringValue = value.toString(); 66 | } 67 | 68 | // Append the key-value pair to the component string 69 | compString += `${key}: ${stringValue}; `; 70 | } 71 | 72 | return compString.trim(); 73 | }; 74 | 75 | /****************************************/ 76 | /*********** String to Math *************/ 77 | /****************************************/ 78 | 79 | /** 80 | * @function 81 | * @memberof string 82 | * @description Converts a string to vector format. 83 | * @param {string} str - the string to be converted to a vector. Must be of format 'xx xxx xx...'. 84 | * @returns {object} - the vector version of the inputted string. 85 | */ 86 | string.stringToVector = function (str) { 87 | return str?.split(' ').map(Number) ?? null; 88 | }; 89 | 90 | string.vectorToString = function (arr) { 91 | let str = ''; 92 | for (let i = 0; i < arr.length; ++i) { 93 | str += arr[i]; 94 | if (i + 1 != arr.length) { 95 | str += ' '; 96 | } 97 | } 98 | return str; 99 | }; 100 | 101 | /** 102 | * @function 103 | * @memberof string 104 | * @description Converts a string to vector format where the numbers are pre-converted from radians to degrees. 105 | * @param {string} str - the string to be converted to a vector. Must be of format 'xx xxx xx...'. 106 | * @returns {object} - the vector version of the inputted string. 107 | */ 108 | string.stringToDegVector = function (str) { 109 | return str.split(' ').map((val) => (parseFloat(val) * Math.PI) / 180); 110 | }; 111 | 112 | /** 113 | * @function 114 | * @memberof string 115 | * @description Converts a string to vector format where the numbers are pre-converted from a number to an appropriate representation 116 | * @param {string} val - the string to be converted to a vector. Must be of format 'x%' or 'x/y'. 117 | * @returns {number} - the vector version of the inputted string. 118 | */ 119 | string.stringToDimensionValue = function (val) { 120 | if (val.includes('%')) { 121 | return parseFloat(val) / 100; 122 | } 123 | if (val.includes('/')) { 124 | return parseInt(val.split('/')[0]) / parseInt(val.split('/')[1]); 125 | } 126 | return val; 127 | }; 128 | 129 | export { string }; 130 | -------------------------------------------------------------------------------- /src/utils/XR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace xr 3 | * @description Useful namespace for helping with xr utility functions. 4 | * this is set within the MRApp to access various WebXR API features 5 | */ 6 | let xr = {}; 7 | 8 | export { xr }; 9 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | // /** 2 | // * @module mrjsUtils 3 | // * @description mrjsUtils module acts as a one stop shop for all global util functions of mrjs. 4 | // * Items can be grabbed by importing as `import { mrjsUtils } from 'mrjs';` and calling them through `mrjsUtils.ParentFile.functionName(...);` 5 | // */ 6 | 7 | // // TODO - this should auto grab instead of manually be updated as manual updates will create problems. 8 | 9 | import { app } from './App.js'; 10 | import { color } from './Color.js'; 11 | import { css } from './CSS.js'; 12 | import { display } from './Display.js'; 13 | import { geometry } from './Geometry.js'; 14 | import { html } from './HTML.js'; 15 | import { js } from './JS.js'; 16 | import { material } from './Material.js'; 17 | import { math } from './Math.js'; 18 | import { model } from './Model.js'; 19 | import { error, warn } from './Notify.js'; 20 | import { physics } from './Physics.js'; 21 | import { string } from './String.js'; 22 | import { xr } from './XR.js'; 23 | 24 | const mrjsUtils = { 25 | app, 26 | color, 27 | css, 28 | display, 29 | error, 30 | geometry, 31 | html, 32 | js, 33 | material, 34 | math, 35 | model, 36 | physics, 37 | string, 38 | warn, 39 | xr, 40 | }; 41 | 42 | export { mrjsUtils }; // Export as named export 43 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | import CopyPlugin from 'copy-webpack-plugin'; 2 | import path, { dirname } from 'path'; 3 | import { fileURLToPath } from 'url'; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | // Determine the environment (e.g., testing or development) 9 | const isTesting = process.env.NODE_ENV === 'development'; 10 | 11 | export default { 12 | entry: { 13 | main: './src/index.js', 14 | }, 15 | output: { 16 | filename: 'mr.js', 17 | path: path.resolve(__dirname, 'dist'), 18 | publicPath: 'auto', 19 | libraryTarget: 'window', 20 | }, 21 | 22 | devServer: { 23 | https: true, 24 | static: { 25 | directory: path.join(__dirname, 'dist'), 26 | }, 27 | client: { 28 | overlay: { 29 | errors: true, 30 | warnings: false, 31 | runtimeErrors: true, 32 | }, 33 | }, 34 | compress: true, 35 | }, 36 | 37 | optimization: { 38 | minimize: false, 39 | }, 40 | 41 | experiments: { 42 | asyncWebAssembly: true, 43 | }, 44 | 45 | resolve: { 46 | extensions: ['.mjs', '.js'], 47 | alias: { 48 | mrjs: path.resolve(__dirname, './src'), // <-- When you build or restart dev-server, you'll get an error if the path to your global.js file is incorrect. 49 | mrjsUtils: path.resolve(__dirname, './src/utils'), 50 | }, 51 | fallback: { 52 | fs: false, 53 | path: false, 54 | }, 55 | fullySpecified: false, // disable required .js / .mjs extensions when importing 56 | }, 57 | 58 | mode: process.env.NODE_ENV || 'development', 59 | 60 | plugins: [ 61 | new CopyPlugin({ 62 | patterns: [ 63 | // make these items generate in dist as default for the runner: index.html, style.css, and assets folder 64 | { from: 'samples/index-assets', to: 'index-assets' }, 65 | { from: 'samples/index.html', to: 'index.html' }, 66 | { from: 'samples/index-style.css', to: 'index-style.css' }, 67 | { from: 'samples/examples', to: 'examples' }, 68 | { from: 'samples/examples-assets', to: 'examples-assets' }, 69 | ], 70 | }), 71 | ], 72 | 73 | module: { 74 | rules: [ 75 | { 76 | test: /\.m?js/, 77 | type: 'javascript/auto', 78 | }, 79 | { 80 | test: /\.m?js/, 81 | resolve: { 82 | fullySpecified: false, 83 | }, 84 | }, 85 | { 86 | test: /\.wasm$/, 87 | type: 'webassembly/async', // or 'webassembly/sync' 88 | }, 89 | { 90 | test: /\.css$/, 91 | use: ['style-loader', 'css-loader'], 92 | }, 93 | { 94 | test: /\.json$/, 95 | use: 'json-loader', 96 | }, 97 | ], 98 | }, 99 | }; 100 | --------------------------------------------------------------------------------