├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── assets │ ├── can-cover-01.png │ ├── particle-01.png │ ├── small-fountain-7073.mp3 │ ├── stoa-noise-01.jpg │ ├── stoa-noise-02.png │ ├── vrh-sandbox-001.glb │ ├── vrhermit_ReadyPlayerMe.jpg │ └── waterbump.png ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── ec-logo.png │ ├── logo.png │ └── tiny-garden-large.png ├── components │ ├── LabLayout.vue │ └── SidebarNav.vue ├── composables │ ├── LabData.js │ ├── TDProjectCard.js │ └── TitleCard.js ├── data │ ├── td-colors.json │ └── td-projects.json ├── lab-shared │ ├── LabCamera.js │ ├── LabColors.js │ ├── LabConsole.js │ ├── LabLights.js │ ├── LabMenu.js │ ├── LabMenuControls.js │ ├── LabNotes.js │ ├── LabPlayer.js │ └── LabRoom.js ├── labs │ ├── Lab000.vue │ ├── Lab001.vue │ ├── Lab002.vue │ ├── Lab003.vue │ ├── Lab004.vue │ ├── Lab005.vue │ ├── Lab006.vue │ ├── Lab007.vue │ ├── Lab008.vue │ ├── Lab009.vue │ ├── Lab010.vue │ ├── Lab011.vue │ ├── Lab012.vue │ ├── Lab014.vue │ ├── Lab015.vue │ ├── Lab016.vue │ ├── Lab017.vue │ ├── Lab018.vue │ ├── Lab019.vue │ ├── Lab020.vue │ ├── Lab021.vue │ ├── Lab022.vue │ ├── Lab023.vue │ ├── Lab024.vue │ ├── Lab025.vue │ ├── Lab026.vue │ ├── Lab027.vue │ ├── Lab028.vue │ ├── Lab029.vue │ ├── Lab030.vue │ ├── Lab031.vue │ └── Lab032.vue ├── main.js ├── router │ └── index.js ├── scenes │ └── Lab000Wrapper.js ├── store │ └── index.js └── views │ ├── About.vue │ └── Home.vue └── vue.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | .certs 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Joseph Simpson 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Canvatorium 2 | 3 | 4 | 5 | **UPDATE: This version of Canvatorium was archived in March of 2023. Please see the project: [Radical Canvatorium](https://github.com/radicalappdev/radical-canvatorium).** 6 | 7 | 8 | ----- 9 | 10 | 11 | An experimental design lab for spatial computing built in Vue JS and Babylon JS. 12 | 13 | Canvatorium is a place where I'm experimenting with UI/UX ideas in WebXR, mostly in the context of VR. 14 | 15 | - Read about the project [here](https://radicalappdev.com/2022/01/20/canvatorium/) 16 | - Issues and ideas can be found [here](https://github.com/users/vrhermit/projects/3/views/4) 17 | 18 | --- 19 | 20 | ## Project setup 21 | 22 | ``` 23 | npm install 24 | ``` 25 | 26 | ### Compiles and hot-reloads for development 27 | 28 | ``` 29 | npm run serve 30 | ``` 31 | 32 | ### Compiles and minifies for production 33 | 34 | ``` 35 | npm run build 36 | ``` 37 | 38 | ## Local network SSL certs 39 | 40 | WebXR scenes need to be served over HTTPS. While working on the project locally, I often need to test the scene on another device such as an Oculus Quest 2. I followed the steps in the article linked below and configured the project with some self-signed certs that will work for local network testing. The browser may still warn or block these certs, but you can click past the warning and access the scene. If you are working with a VR headset attached to your PC, then you can remove the https object from `vue.config.js`. 41 | 42 | Alternative: you can use ngrok to forward your local dev server to a URL. I found this to be really slow and prone to errors... 43 | 44 | Used for testing the WebXR scene on an Oculus Quest while connected to the dev server over the local network. 45 | More info: https://bharathvaj.me/blog/use-ssl-with-vue-cli-locally 46 | 47 | run 48 | 49 | ``` 50 | mkdir -p .certs 51 | ``` 52 | 53 | then 54 | 55 | ``` 56 | mkcert -key-file ./.certs/key.pem -cert-file ./.certs/cert.pem "localhost" 57 | ``` 58 | 59 | If you don't want to use these certs just disable the following lines in `vue.config.js` 60 | 61 | ``` 62 | https: { 63 | key: fs.readFileSync(".certs/key.pem"), 64 | cert: fs.readFileSync(".certs/cert.pem") 65 | }, 66 | ``` 67 | 68 | ### Babylon JS Stable 69 | 70 | Install the latest Stable version Babylon JS 71 | 72 | ``` 73 | npm install --save babylonjs babylonjs-gui babylonjs-loaders 74 | npm install --save babylonjs-materials 75 | npm install --save babylonjs-procedural-textures 76 | npm install --save-dev babylonjs-inspector 77 | 78 | ``` 79 | 80 | ### Babylon JS Preview 81 | 82 | Babylon JS 5.0 is in beta as of time of writing. With updates shipping often, it find it best to install all of the dependencies at once after a new release. 83 | Install the latest Babylon JS 5.0 Preview 84 | 85 | ``` 86 | npm install --save babylonjs@preview babylonjs-gui@preview babylonjs-loaders@preview 87 | npm install --save babylonjs-materials@preview 88 | npm install --save babylonjs-procedural-textures@preview 89 | npm install --save-dev babylonjs-inspector@preview 90 | 91 | ``` 92 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "canvatorium", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@vueuse/core": "^7.7.1", 12 | "babylonjs": "^5.0.0-rc.13", 13 | "babylonjs-gui": "^5.0.0-rc.13", 14 | "babylonjs-loaders": "^5.0.0-rc.13", 15 | "babylonjs-materials": "^5.0.0-rc.13", 16 | "babylonjs-procedural-textures": "^5.0.0-rc.13", 17 | "core-js": "^3.21.1", 18 | "marked": "^4.0.12", 19 | "vue": "^3.2.31", 20 | "vue-router": "^4.0.14", 21 | "vuex": "^4.0.0-0" 22 | }, 23 | "devDependencies": { 24 | "@vue/cli-plugin-babel": "^4.5.16", 25 | "@vue/cli-plugin-eslint": "^4.5.16", 26 | "@vue/cli-plugin-router": "^4.5.16", 27 | "@vue/cli-plugin-vuex": "^4.5.16", 28 | "@vue/cli-service": "^4.5.16", 29 | "@vue/compiler-sfc": "^3.2.31", 30 | "babel-eslint": "^10.1.0", 31 | "babylonjs-inspector": "^5.0.0-rc.13", 32 | "eslint": "^6.7.2", 33 | "eslint-plugin-vue": "^7.0.0" 34 | }, 35 | "eslintConfig": { 36 | "root": true, 37 | "env": { 38 | "node": true 39 | }, 40 | "extends": [ 41 | "plugin:vue/vue3-essential", 42 | "eslint:recommended" 43 | ], 44 | "parserOptions": { 45 | "parser": "babel-eslint" 46 | }, 47 | "rules": {} 48 | }, 49 | "browserslist": [ 50 | "> 1%", 51 | "last 2 versions", 52 | "not dead" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /public/assets/can-cover-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radicalappdev/Canvatorium/85fe9344b3f943775286fba0daf2307456e2b7c6/public/assets/can-cover-01.png -------------------------------------------------------------------------------- /public/assets/particle-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radicalappdev/Canvatorium/85fe9344b3f943775286fba0daf2307456e2b7c6/public/assets/particle-01.png -------------------------------------------------------------------------------- /public/assets/small-fountain-7073.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radicalappdev/Canvatorium/85fe9344b3f943775286fba0daf2307456e2b7c6/public/assets/small-fountain-7073.mp3 -------------------------------------------------------------------------------- /public/assets/stoa-noise-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radicalappdev/Canvatorium/85fe9344b3f943775286fba0daf2307456e2b7c6/public/assets/stoa-noise-01.jpg -------------------------------------------------------------------------------- /public/assets/stoa-noise-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radicalappdev/Canvatorium/85fe9344b3f943775286fba0daf2307456e2b7c6/public/assets/stoa-noise-02.png -------------------------------------------------------------------------------- /public/assets/vrh-sandbox-001.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radicalappdev/Canvatorium/85fe9344b3f943775286fba0daf2307456e2b7c6/public/assets/vrh-sandbox-001.glb -------------------------------------------------------------------------------- /public/assets/vrhermit_ReadyPlayerMe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radicalappdev/Canvatorium/85fe9344b3f943775286fba0daf2307456e2b7c6/public/assets/vrhermit_ReadyPlayerMe.jpg -------------------------------------------------------------------------------- /public/assets/waterbump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radicalappdev/Canvatorium/85fe9344b3f943775286fba0daf2307456e2b7c6/public/assets/waterbump.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radicalappdev/Canvatorium/85fe9344b3f943775286fba0daf2307456e2b7c6/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | <%= htmlWebpackPlugin.options.title %> 19 | 20 | 21 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 33 | 34 | 82 | 83 | -------------------------------------------------------------------------------- /src/assets/ec-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radicalappdev/Canvatorium/85fe9344b3f943775286fba0daf2307456e2b7c6/src/assets/ec-logo.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radicalappdev/Canvatorium/85fe9344b3f943775286fba0daf2307456e2b7c6/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/tiny-garden-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radicalappdev/Canvatorium/85fe9344b3f943775286fba0daf2307456e2b7c6/src/assets/tiny-garden-large.png -------------------------------------------------------------------------------- /src/components/LabLayout.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/SidebarNav.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 34 | 35 | > 57 | -------------------------------------------------------------------------------- /src/composables/LabData.js: -------------------------------------------------------------------------------- 1 | import { ref } from "@vue/runtime-core"; 2 | 3 | export const labNotes = ref(""); 4 | -------------------------------------------------------------------------------- /src/composables/TDProjectCard.js: -------------------------------------------------------------------------------- 1 | import * as BABYLON from "babylonjs"; 2 | import tdcolors from "../data/td-colors.json"; 3 | 4 | export const createProjectCard = (project, scene) => { 5 | const { id, name, color } = project; 6 | const cardWidth = 2; 7 | const cardHeight = 0.4; 8 | 9 | // Card 10 | const projectCard = new BABYLON.MeshBuilder.CreateBox(`project-card-${id}`, { 11 | width: cardWidth, 12 | height: cardHeight, 13 | depth: 0.1 14 | }); 15 | projectCard.position = new BABYLON.Vector3(0, 1, 0); 16 | 17 | const projectCardMaterial = new BABYLON.StandardMaterial("projectCardMaterial", scene); 18 | projectCardMaterial.diffuseColor = new BABYLON.Color3.FromHexString("#a5b1c2"); 19 | projectCardMaterial.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2); 20 | projectCard.material = projectCardMaterial; 21 | 22 | // Card Content - Color Dot 23 | const projectDot = BABYLON.MeshBuilder.CreateCylinder(`project-sphere-${id}`, { 24 | diameter: 0.25, 25 | height: 0.05 26 | }); 27 | const projectDotMaterial = new BABYLON.StandardMaterial(`project-sphere-mat-${id}`, scene); 28 | projectDotMaterial.diffuseColor = new BABYLON.Color3.FromHexString(tdcolors[color]); 29 | projectDotMaterial.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2); 30 | projectDot.material = projectDotMaterial; 31 | projectDot.parent = projectCard; 32 | projectDot.position = new BABYLON.Vector3(-0.8, 0, -0.056); 33 | projectDot.rotation = new BABYLON.Vector3(Math.PI / 2, 0, 0); 34 | projectDot.scaling = new BABYLON.Vector3(0.8, 0.8, 0.8); 35 | 36 | // Card Content - Dynamic Texture 37 | const plane = BABYLON.MeshBuilder.CreatePlane(`plane-${id}`, { width: cardWidth, height: cardHeight }, scene); 38 | plane.parent = projectCard; 39 | plane.position = new BABYLON.Vector3(0, 0, -0.056); 40 | //Set width and height for dynamic texture using same multiplier 41 | const dtResolution = 512; 42 | const dtWidth = cardWidth * dtResolution; 43 | const dtHeight = cardHeight * dtResolution; 44 | 45 | //Set text 46 | 47 | //Create dynamic texture 48 | var dynamicTexture = new BABYLON.DynamicTexture(`project-dt-${id}`, { width: dtWidth, height: dtHeight }, scene); 49 | 50 | //set font to be actually used to write text on dynamic texture 51 | var font = "72px Tahoma"; 52 | 53 | //Draw text 54 | dynamicTexture.drawText(name, 196, null, font, "#2a323e", "#d3d9e1", true); 55 | 56 | //create material 57 | var mat = new BABYLON.StandardMaterial("mat", scene); 58 | mat.diffuseTexture = dynamicTexture; 59 | 60 | //apply material 61 | plane.material = mat; 62 | 63 | projectCard.scaling = new BABYLON.Vector3(0.4, 0.4, 0.4); 64 | projectCard.addBehavior(new BABYLON.SixDofDragBehavior()); 65 | return projectCard; 66 | }; 67 | -------------------------------------------------------------------------------- /src/composables/TitleCard.js: -------------------------------------------------------------------------------- 1 | import * as BABYLON from "babylonjs"; 2 | import * as GUI from "babylonjs-gui"; 3 | import { ref, watch } from "@vue/runtime-core"; 4 | import LabColors from "../lab-shared/LabColors"; 5 | 6 | const createTitleCard = (scene) => { 7 | // The data that we will display in the VR console 8 | let title = ref("TITLE PLACEHOLDER"); 9 | let subtitle = ref("SUBTITLE PLACEHOLDER"); 10 | 11 | const card = BABYLON.MeshBuilder.CreateBox("detail-card", { 12 | height: 1, 13 | width: 1.6, 14 | depth: 0.2 15 | }); 16 | card.position = new BABYLON.Vector3(0, 1.5, 0); 17 | card.scaling = new BABYLON.Vector3(0.8, 0.8, 0.8); 18 | 19 | const cardMaterial = new BABYLON.StandardMaterial("card-material", scene); 20 | cardMaterial.diffuseColor = LabColors["dark3"]; 21 | cardMaterial.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2); 22 | card.material = cardMaterial; 23 | 24 | const plane = BABYLON.MeshBuilder.CreatePlane("detail-plane", { height: 1, width: 1.6 }, scene); 25 | plane.position.z = -0.11; 26 | plane.parent = card; 27 | 28 | const advancedTexture = GUI.AdvancedDynamicTexture.CreateForMesh(plane, 1.6 * 1024, 1 * 1024); 29 | advancedTexture.name = "title-card-texture"; 30 | 31 | const titleTextBlock = new GUI.TextBlock("title-text"); 32 | titleTextBlock.text = title.value; 33 | titleTextBlock.textWrapping = true; 34 | titleTextBlock.height = 0.5; 35 | titleTextBlock.top = -244; 36 | titleTextBlock.color = "#d3d9e1"; 37 | titleTextBlock.fontSize = "124px"; 38 | titleTextBlock.fontStyle = "bold"; 39 | advancedTexture.addControl(titleTextBlock); 40 | 41 | const subtitleTextBlock = new GUI.TextBlock("subtitle-text"); 42 | subtitleTextBlock.text = subtitle.value; 43 | subtitleTextBlock.textWrapping = true; 44 | subtitleTextBlock.height = 0.5; 45 | subtitleTextBlock.color = "#d3d9e1"; 46 | subtitleTextBlock.fontSize = "96px"; 47 | subtitleTextBlock.fontStyle = "bold"; 48 | advancedTexture.addControl(subtitleTextBlock); 49 | 50 | // Watch the title and subtitle for changes 51 | // and update the text in the GUI 52 | watch(title, (newValue) => { 53 | const texture = scene.getTextureByName("title-card-texture"); 54 | texture.getControlByName("title-text").text = newValue; 55 | }); 56 | 57 | watch(subtitle, (newValue) => { 58 | const texture = scene.getTextureByName("title-card-texture"); 59 | texture.getControlByName("subtitle-text").text = newValue; 60 | }); 61 | 62 | return { title, subtitle }; 63 | }; 64 | 65 | export default createTitleCard; 66 | -------------------------------------------------------------------------------- /src/data/td-colors.json: -------------------------------------------------------------------------------- 1 | { 2 | "30": "#b8256f", 3 | "31": "#db4035", 4 | "32": "#ff9933", 5 | "33": "#fad000", 6 | "34": "#afb83b", 7 | "35": "#7ecc49", 8 | "36": "#299438", 9 | "37": "#6accbc", 10 | "38": "#158fad", 11 | "39": "#14aaf5", 12 | "40": "#96c3eb", 13 | "41": "#4073ff", 14 | "42": "#884dff", 15 | "43": "#af38eb", 16 | "44": "#eb96eb", 17 | "45": "#e05194", 18 | "46": "#ff8d85", 19 | "47": "#808080", 20 | "48": "#b8b8b8", 21 | "49": "#b8b8b8" 22 | } 23 | -------------------------------------------------------------------------------- /src/data/td-projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 2285537901, 4 | "color": 48, 5 | "name": "Inbox", 6 | "commentCount": 0, 7 | "shared": false, 8 | "favorite": false, 9 | "syncId": 0, 10 | "inboxProject": true, 11 | "url": "https://todoist.com/showProject?id=2285537901" 12 | }, 13 | { 14 | "id": 2285537936, 15 | "order": 1, 16 | "color": 42, 17 | "name": "Chambers", 18 | "commentCount": 0, 19 | "shared": false, 20 | "favorite": false, 21 | "syncId": 0, 22 | "url": "https://todoist.com/showProject?id=2285537936" 23 | }, 24 | { 25 | "id": 2285537965, 26 | "order": 2, 27 | "color": 38, 28 | "name": "ToDoist API Features", 29 | "commentCount": 0, 30 | "shared": false, 31 | "favorite": false, 32 | "syncId": 0, 33 | "url": "https://todoist.com/showProject?id=2285537965" 34 | }, 35 | { 36 | "id": 2285539058, 37 | "order": 3, 38 | "color": 41, 39 | "name": "Scene Features", 40 | "commentCount": 0, 41 | "shared": false, 42 | "favorite": false, 43 | "syncId": 0, 44 | "url": "https://todoist.com/showProject?id=2285539058" 45 | }, 46 | { 47 | "id": 2285797146, 48 | "order": 5, 49 | "color": 46, 50 | "name": "Canvatorium", 51 | "commentCount": 0, 52 | "shared": true, 53 | "favorite": false, 54 | "syncId": 9385368, 55 | "url": "https://todoist.com/showProject?id=2285797146&sync_id=9385368" 56 | }, 57 | { 58 | "id": 2285797183, 59 | "order": 6, 60 | "color": 46, 61 | "name": "Extended Collection", 62 | "commentCount": 0, 63 | "shared": true, 64 | "favorite": false, 65 | "syncId": 9385383, 66 | "url": "https://todoist.com/showProject?id=2285797183&sync_id=9385383" 67 | } 68 | ] 69 | -------------------------------------------------------------------------------- /src/lab-shared/LabCamera.js: -------------------------------------------------------------------------------- 1 | import * as BABYLON from "babylonjs"; 2 | 3 | const addLabCamera = (canvas, scene) => { 4 | // Add an ArcRotateCamera to the scene and attach it to the canvas 5 | // ArcRotateCamera is used to rotate the camera around the scene when not using WebXR 6 | const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 3, new BABYLON.Vector3(0, 0, 0), scene); 7 | camera.wheelDeltaPercentage = 0.01; 8 | camera.upperBetaLimit = Math.PI / 1.5; 9 | camera.lowerRadiusLimit = 2; 10 | camera.upperRadiusLimit = 128; 11 | camera.setPosition(new BABYLON.Vector3(0, 3.5, -6)); 12 | camera.setTarget(new BABYLON.Vector3(0, 1, 0)); 13 | camera.attachControl(canvas, true); 14 | }; 15 | export default addLabCamera; 16 | -------------------------------------------------------------------------------- /src/lab-shared/LabColors.js: -------------------------------------------------------------------------------- 1 | // import { BABYLON.Color3 } from "@babylonjs/core"; 2 | import * as BABYLON from "babylonjs"; 3 | 4 | const LabColors = { 5 | purple: new BABYLON.Color3.FromHexString("#8854d0"), 6 | blue: new BABYLON.Color3.FromHexString("#3867d6"), 7 | teal: new BABYLON.Color3.FromHexString("#2d98da"), 8 | cyan: new BABYLON.Color3.FromHexString("#0fb9b1"), 9 | 10 | green: new BABYLON.Color3.FromHexString("#20bf6b"), 11 | yellow: new BABYLON.Color3.FromHexString("#f7b731"), 12 | orange: new BABYLON.Color3.FromHexString("#fa8231"), 13 | red: new BABYLON.Color3.FromHexString("#eb3b5a"), 14 | 15 | dark1: new BABYLON.Color3.FromHexString("#2a323e"), 16 | dark2: new BABYLON.Color3.FromHexString("#3e4a5d"), 17 | dark3: new BABYLON.Color3.FromHexString("#49576c"), 18 | dark4: new BABYLON.Color3.FromHexString("#53637b"), 19 | 20 | light1: new BABYLON.Color3.FromHexString("#a5b1c2"), 21 | light2: new BABYLON.Color3.FromHexString("#b4becc"), 22 | light3: new BABYLON.Color3.FromHexString("#c3cbd7"), 23 | light4: new BABYLON.Color3.FromHexString("#d3d9e1") 24 | }; 25 | 26 | export default LabColors; 27 | -------------------------------------------------------------------------------- /src/lab-shared/LabConsole.js: -------------------------------------------------------------------------------- 1 | /* 2 | This tool started as a proof of concept for during Lab 007 and was refactored into this file. 3 | Lots to do before this is a robust tool: https://github.com/vrhermit/Canvatorium/issues/10 4 | */ 5 | 6 | import * as BABYLON from "babylonjs"; 7 | import * as GUI from "babylonjs-gui"; 8 | import { ref, reactive, watch } from "@vue/runtime-core"; 9 | import LabColors from "../lab-shared/LabColors"; 10 | 11 | export const createLabConsolePanel = () => { 12 | // The data that we will display in the VR console 13 | let conLogData = reactive([]); 14 | 15 | // A reference to the BJS GUI Scroll Viewer, too lazy to query this in the graph... 16 | let scrollViewer; 17 | 18 | // A reference to the BJS GUI TextBlock, too lazy to query this in the graph... 19 | let loggerText; 20 | 21 | // Adapted from https://ourcodeworld.com/articles/read/104/how-to-override-the-console-methods-in-javascript 22 | const overrideConsole = () => { 23 | // Save the original method in a private variable 24 | let _privateLog = console.log; 25 | // Redefine console.log method with a custom function 26 | console.log = function (message) { 27 | conLogData.push(message.toString()); 28 | _privateLog.apply(console, arguments); 29 | }; 30 | }; 31 | overrideConsole(); 32 | 33 | var panel = new GUI.StackPanel(); 34 | panel.height = `${1024 - 128}px`; 35 | 36 | var sv = new GUI.ScrollViewer("logger-scroll"); 37 | scrollViewer = sv; 38 | sv.thickness = 12; 39 | sv.color = "#3e4a5d"; 40 | sv.background = "#3e4a5d"; 41 | sv.opacity = 1; 42 | sv.width = `${1024}px`; 43 | sv.height = `${1024 - 196}px`; 44 | sv.barSize = 20; 45 | sv.barColor = "#53637b"; 46 | sv.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 47 | 48 | panel.addControl(sv); 49 | 50 | var tb = new GUI.TextBlock("logger-text"); 51 | loggerText = tb; 52 | tb.textWrapping = true; 53 | 54 | tb.width = 1; 55 | tb.height = 3; 56 | tb.paddingTop = "1%"; 57 | tb.paddingLeft = "12px"; 58 | tb.paddingRight = "12px"; 59 | tb.paddingBottom = "1%"; 60 | tb.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 61 | tb.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 62 | tb.textHorizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 63 | tb.textVerticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 64 | tb.color = "#d3d9e1"; 65 | tb.fontSize = "36px"; 66 | 67 | sv.addControl(tb); 68 | 69 | var clearButton = GUI.Button.CreateSimpleButton("clear-button", "clear"); 70 | clearButton.width = "128px"; 71 | clearButton.height = "64px"; 72 | clearButton.color = "white"; 73 | clearButton.background = "#eb3b5a"; 74 | clearButton.fontSize = "48px"; 75 | clearButton.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT; 76 | clearButton.onPointerClickObservable.add(() => { 77 | conLogData.splice(0, conLogData.length); 78 | console.log("console cleared"); 79 | }); 80 | panel.addControl(clearButton); 81 | 82 | // Watch the labLog data and update the text in the GUI 83 | // TODO: Refactor this to append only the new eleements to the text block 84 | watch(conLogData, (newValue) => { 85 | const logData = [...newValue]; 86 | if (scrollViewer && loggerText) { 87 | loggerText.text = logData.join("\n"); 88 | loggerText.resizeToFit = true; 89 | scrollViewer.verticalBar.value = 1; 90 | } 91 | }); 92 | 93 | return panel; 94 | }; 95 | 96 | export const createLabConsole = (scene) => { 97 | // The data that we will display in the VR console 98 | let conLogData = reactive([]); 99 | let consoleIsVisible = ref(false); 100 | 101 | // A reference to the BJS GUI Scroll Viewer, too lazy to query this in the graph... 102 | let scrollViewer; 103 | 104 | // A reference to the BJS GUI TextBlock, too lazy to query this in the graph... 105 | let loggerText; 106 | 107 | // Adapted from https://ourcodeworld.com/articles/read/104/how-to-override-the-console-methods-in-javascript 108 | const overrideConsole = () => { 109 | // Save the original method in a private variable 110 | let _privateLog = console.log; 111 | // Redefine console.log method with a custom function 112 | console.log = function (message) { 113 | conLogData.push(message.toString()); 114 | _privateLog.apply(console, arguments); 115 | }; 116 | }; 117 | overrideConsole(); 118 | 119 | // GUI 120 | const grabMaterial = new BABYLON.StandardMaterial("card-material", scene); 121 | grabMaterial.diffuseColor = LabColors["purple"]; 122 | grabMaterial.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2); 123 | const grab = BABYLON.MeshBuilder.CreateBox("detail-card", { 124 | height: 0.05, 125 | width: 0.4, 126 | depth: 0.05 127 | }); 128 | grab.material = grabMaterial; 129 | grab.position = new BABYLON.Vector3(0, 1, 0); 130 | // var boundingBox = BABYLON.BoundingBoxGizmo.MakeNotPickableAndWrapInBoundingBox(grab); 131 | // boundingBox.ignoreChildren = true; 132 | const sixDofDragBehavior = new BABYLON.SixDofDragBehavior(); 133 | // sixDofDragBehavior.allowMultiPointers = true; 134 | grab.addBehavior(sixDofDragBehavior); 135 | 136 | const card = BABYLON.MeshBuilder.CreateBox("detail-card", { 137 | height: 2.1, 138 | width: 3.1, 139 | depth: 0.2 140 | }); 141 | // card.parent = grab; 142 | grab.addChild(card); 143 | // card.position = new BABYLON.Vector3(-1, 1, 1); 144 | card.position = new BABYLON.Vector3(0, 0.6, 0); 145 | card.scaling = new BABYLON.Vector3(0.5, 0.5, 0.5); 146 | const cardMaterial = new BABYLON.StandardMaterial("card-material", scene); 147 | cardMaterial.diffuseColor = LabColors["light1"]; 148 | cardMaterial.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2); 149 | card.material = cardMaterial; 150 | 151 | const plane = BABYLON.MeshBuilder.CreatePlane("detail-plane", { height: 2, width: 3 }, scene); 152 | plane.position.z = -0.11; 153 | plane.parent = card; 154 | 155 | grab.isVisible = consoleIsVisible.value; 156 | card.isVisible = consoleIsVisible.value; 157 | plane.isVisible = consoleIsVisible.value; 158 | 159 | const advancedTexture = GUI.AdvancedDynamicTexture.CreateForMesh(plane, 3 * 1024, 2 * 1024); 160 | advancedTexture.name = "logger-texture"; 161 | 162 | var panel = new GUI.StackPanel(); 163 | advancedTexture.addControl(panel); 164 | 165 | var sv = new GUI.ScrollViewer("logger-scroll"); 166 | scrollViewer = sv; 167 | sv.thickness = 48; 168 | sv.color = "#3e4a5d"; 169 | sv.background = "#3e4a5d"; 170 | sv.opacity = 1; 171 | sv.width = `${3 * 1024}px`; 172 | sv.height = `${2 * 1024 - 128}px`; 173 | sv.barSize = 60; 174 | sv.barColor = "#53637b"; 175 | sv.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 176 | 177 | panel.addControl(sv); 178 | 179 | var tb = new GUI.TextBlock("logger-text"); 180 | loggerText = tb; 181 | tb.textWrapping = true; 182 | 183 | tb.width = 1; 184 | tb.height = 3; 185 | tb.paddingTop = "1%"; 186 | tb.paddingLeft = "30px"; 187 | tb.paddingRight = "20px"; 188 | tb.paddingBottom = "1%"; 189 | tb.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 190 | tb.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 191 | tb.textHorizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 192 | tb.textVerticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 193 | tb.color = "#d3d9e1"; 194 | tb.fontSize = "96px"; 195 | 196 | sv.addControl(tb); 197 | 198 | var clearButton = GUI.Button.CreateSimpleButton("clear-button", "clear"); 199 | clearButton.width = "256px"; 200 | clearButton.height = "128px"; 201 | clearButton.color = "white"; 202 | clearButton.background = "#eb3b5a"; 203 | clearButton.fontSize = "96px"; 204 | clearButton.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT; 205 | clearButton.onPointerClickObservable.add(() => { 206 | conLogData.splice(0, conLogData.length); 207 | console.log("console cleared"); 208 | }); 209 | panel.addControl(clearButton); 210 | 211 | const setConsoleTransform = (position, rotateTo, scaling) => { 212 | grab.position = position; 213 | grab.lookAt(rotateTo, Math.PI, 0, 0); 214 | grab.scaling = scaling; 215 | }; 216 | 217 | const toggleConsole = (xrController) => { 218 | consoleIsVisible.value = !consoleIsVisible.value; 219 | 220 | if (xrController.grip && consoleIsVisible.value) { 221 | // Create an empty ray 222 | const tmpRay = new BABYLON.Ray(new BABYLON.Vector3(), new BABYLON.Vector3(), Infinity); 223 | 224 | // Update the ray to use the controller's position and forward 225 | xrController.getWorldPointerRayToRef(tmpRay, true); 226 | 227 | // Calculate a position in front of the controller 228 | const newPosition = new BABYLON.Vector3(tmpRay.origin.x + tmpRay.direction.x, tmpRay.origin.y, tmpRay.origin.z + tmpRay.direction.z); 229 | 230 | // Use the current position of the controller as a vector to use with lookAt() 231 | const newRotation = new BABYLON.Vector3(tmpRay.origin.x, tmpRay.origin.y, tmpRay.origin.z); 232 | 233 | const newScale = new BABYLON.Vector3(0.5, 0.5, 0.5); 234 | setConsoleTransform( 235 | // Repacking these so I don't end up with a reference to the controller 236 | newPosition, 237 | newRotation, 238 | newScale 239 | ); 240 | } 241 | }; 242 | 243 | // Watch the labLog data and update the text in the GUI 244 | // TODO: Refactor this to append only the new eleements to the text block 245 | watch(conLogData, (newValue) => { 246 | const logData = [...newValue]; 247 | if (scrollViewer && loggerText) { 248 | loggerText.text = logData.join("\n"); 249 | loggerText.resizeToFit = true; 250 | scrollViewer.verticalBar.value = 1; 251 | } 252 | }); 253 | 254 | watch(consoleIsVisible, (newValue) => { 255 | grab.isVisible = newValue; 256 | card.isVisible = newValue; 257 | plane.isVisible = newValue; 258 | }); 259 | 260 | return { toggleConsole }; 261 | }; 262 | -------------------------------------------------------------------------------- /src/lab-shared/LabLights.js: -------------------------------------------------------------------------------- 1 | import * as BABYLON from "babylonjs"; 2 | import LabColors from "./LabColors"; 3 | 4 | const addLabLights = (scene) => { 5 | // Customize the scene lighting and background color 6 | const ambientLight1 = new BABYLON.HemisphericLight("light-01", new BABYLON.Vector3(5, 5, 5), scene); 7 | ambientLight1.intensity = 0.8; 8 | const ambientLight2 = new BABYLON.HemisphericLight("light-02", new BABYLON.Vector3(-5, 5, -5), scene); 9 | ambientLight2.intensity = 0.8; 10 | scene.clearColor = LabColors["dark1"]; 11 | }; 12 | export default addLabLights; 13 | -------------------------------------------------------------------------------- /src/lab-shared/LabMenu.js: -------------------------------------------------------------------------------- 1 | /* 2 | This tool started as a proof of concept for during Lab 007 and was refactored into this file. 3 | Lots to do before this is a robust tool: https://github.com/vrhermit/Canvatorium/issues/10 4 | */ 5 | 6 | import * as BABYLON from "babylonjs"; 7 | import * as GUI from "babylonjs-gui"; 8 | 9 | import { ref, watch } from "@vue/runtime-core"; 10 | import LabColors from "../lab-shared/LabColors"; 11 | import { createLabConsolePanel } from "../lab-shared/LabConsole"; 12 | import createLabNotesPanel from "../lab-shared/LabNotes"; 13 | 14 | export const createLabMenu = (scene) => { 15 | let menuIsVisible = ref(false); 16 | let currentTabContent = ref("tab1"); 17 | 18 | const tabContent1 = createLabNotesPanel(); 19 | const tabContent2 = createLabConsolePanel(); 20 | console.log("tab content ready", tabContent1, tabContent2); 21 | 22 | // Grab Handle 23 | const grabMaterial = new BABYLON.StandardMaterial("card-material", scene); 24 | grabMaterial.diffuseColor = LabColors["purple"]; 25 | grabMaterial.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2); 26 | const grab = BABYLON.MeshBuilder.CreateBox("detail-card", { 27 | height: 0.05, 28 | width: 0.4, 29 | depth: 0.05 30 | }); 31 | grab.material = grabMaterial; 32 | grab.position = new BABYLON.Vector3(0, 1, 0); 33 | 34 | const sixDofDragBehavior = new BABYLON.SixDofDragBehavior(); 35 | sixDofDragBehavior.draggableMeshes = [grab]; 36 | grab.addBehavior(sixDofDragBehavior); 37 | 38 | // Card 39 | const cardMaterial = new BABYLON.StandardMaterial("menu-card-material", scene); 40 | cardMaterial.diffuseColor = LabColors["light1"]; 41 | cardMaterial.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2); 42 | 43 | const card = BABYLON.MeshBuilder.CreateBox("menu-card", { 44 | width: 1, 45 | height: 1, 46 | depth: 0.1 47 | }); 48 | card.material = cardMaterial; 49 | grab.addChild(card); 50 | card.position = new BABYLON.Vector3(0, 0.6, 0); 51 | 52 | // UI Plane 53 | const plane = BABYLON.MeshBuilder.CreatePlane( 54 | "menu-plane", 55 | { 56 | width: 1, 57 | height: 1 58 | }, 59 | scene 60 | ); 61 | plane.position.z = -0.055; 62 | plane.parent = card; 63 | 64 | const advancedTexture = GUI.AdvancedDynamicTexture.CreateForMesh(plane, 1 * 1024, 1 * 1024); 65 | advancedTexture.name = "menu-texture"; 66 | 67 | var vStack = new GUI.StackPanel(); 68 | advancedTexture.addControl(vStack); 69 | 70 | var hStack = new GUI.StackPanel(); 71 | hStack.isVertical = false; 72 | hStack.height = "128px"; 73 | 74 | var buttonInfo = GUI.Button.CreateSimpleButton("buttonInfo", "Lab Info"); 75 | buttonInfo.width = "512px"; 76 | buttonInfo.height = "128px"; 77 | buttonInfo.paddingBottomInPixels = "12px"; 78 | buttonInfo.background = "#3e4a5d"; 79 | buttonInfo.color = "white"; 80 | buttonInfo.fontSize = "48px"; 81 | buttonInfo.left = "-256px"; 82 | buttonInfo.onPointerDownObservable.add(() => { 83 | currentTabContent.value = "tab1"; 84 | }); 85 | hStack.addControl(buttonInfo); 86 | 87 | var buttonConsole = GUI.Button.CreateSimpleButton("buttonConsole", "Console"); 88 | buttonConsole.width = "512px"; 89 | buttonConsole.height = "128px"; 90 | buttonConsole.paddingBottomInPixels = "12px"; 91 | buttonConsole.background = "#3e4a5d"; 92 | buttonConsole.color = "white"; 93 | buttonConsole.fontSize = "48px"; 94 | buttonConsole.left = "256px"; 95 | buttonConsole.onPointerDownObservable.add(() => { 96 | currentTabContent.value = "tab2"; 97 | }); 98 | hStack.addControl(buttonConsole); 99 | 100 | vStack.addControl(hStack); 101 | 102 | vStack.addControl(tabContent1); 103 | tabContent2.isVisible = false; 104 | vStack.addControl(tabContent2); 105 | 106 | const setConsoleTransform = (position, rotateTo, scaling) => { 107 | grab.position = position; 108 | grab.lookAt(rotateTo, Math.PI, 0, 0); 109 | grab.scaling = scaling; 110 | }; 111 | 112 | const toggleMenu = (xrController) => { 113 | menuIsVisible.value = !menuIsVisible.value; 114 | 115 | if (xrController.grip && menuIsVisible.value) { 116 | // Create an empty ray 117 | const tmpRay = new BABYLON.Ray(new BABYLON.Vector3(), new BABYLON.Vector3(), Infinity); 118 | 119 | // Update the ray to use the controller's position and forward 120 | xrController.getWorldPointerRayToRef(tmpRay, true); 121 | 122 | // Calculate a position in front of the controller 123 | const newPosition = new BABYLON.Vector3(tmpRay.origin.x + tmpRay.direction.x, tmpRay.origin.y, tmpRay.origin.z + tmpRay.direction.z); 124 | 125 | // Use the current position of the controller as a vector to use with lookAt() 126 | const newRotation = new BABYLON.Vector3(tmpRay.origin.x, tmpRay.origin.y, tmpRay.origin.z); 127 | 128 | const newScale = new BABYLON.Vector3(0.5, 0.5, 0.5); 129 | setConsoleTransform( 130 | // Repacking these so I don't end up with a reference to the controller 131 | newPosition, 132 | newRotation, 133 | newScale 134 | ); 135 | } 136 | }; 137 | 138 | grab.isVisible = false; 139 | card.isVisible = false; 140 | plane.isVisible = false; 141 | 142 | document.onkeydown = (e) => { 143 | if (e.code == "KeyY") { 144 | menuIsVisible.value = !menuIsVisible.value; 145 | } 146 | }; 147 | 148 | watch(menuIsVisible, (newValue) => { 149 | grab.isVisible = newValue; 150 | card.isVisible = newValue; 151 | plane.isVisible = newValue; 152 | }); 153 | 154 | watch(currentTabContent, (newValue) => { 155 | if (newValue === "tab1") { 156 | console.log("tab1"); 157 | tabContent2.isVisible = false; 158 | tabContent1.isVisible = true; 159 | } else if (newValue === "tab2") { 160 | console.log("tab2"); 161 | tabContent1.isVisible = false; 162 | tabContent2.isVisible = true; 163 | } 164 | }); 165 | 166 | return { toggleMenu }; 167 | }; 168 | -------------------------------------------------------------------------------- /src/lab-shared/LabMenuControls.js: -------------------------------------------------------------------------------- 1 | // import * as BABYLON from "babylonjs"; 2 | import * as GUI from "babylonjs-gui"; 3 | 4 | const createGridMenuLabel = (text) => { 5 | const label = new GUI.TextBlock(); 6 | label.text = text; 7 | label.height = "60px"; 8 | label.fontSize = "40px"; 9 | label.color = "white"; 10 | label.textHorizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 11 | return label; 12 | }; 13 | 14 | const createGridMenuSlider = (options) => { 15 | const { min, max, step, value } = options; 16 | const slider = new GUI.Slider(); 17 | slider.minimum = min; 18 | slider.maximum = max; 19 | slider.step = step; 20 | slider.value = value; 21 | slider.height = "60px"; 22 | slider.width = "100%"; 23 | slider.color = "#8854d0"; 24 | slider.background = "#53637b"; 25 | slider.thumbWidth = "60px"; 26 | slider.thumbHeight = "60px"; 27 | slider.thumbBackground = "#8854d0"; 28 | slider.thumbBorderColor = "#8854d0"; 29 | slider.thumbBorderWidth = "2px"; 30 | slider.isThumbCircle = true; 31 | slider.isThumbClamped = true; 32 | slider.isThumbClampedY = true; 33 | 34 | return slider; 35 | }; 36 | 37 | const createGridMenuCheckbox = () => { 38 | const checkbox = new GUI.Checkbox(); 39 | checkbox.isChecked = true; 40 | checkbox.height = "60px"; 41 | // checkbox does not have a margin, so add some extra width, then use it in padding 42 | checkbox.width = "70px"; 43 | checkbox.paddingLeftInPixels = "10"; 44 | checkbox.color = "#8854d0"; 45 | checkbox.background = "#53637b"; 46 | checkbox.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 47 | return checkbox; 48 | }; 49 | 50 | export { createGridMenuLabel, createGridMenuSlider, createGridMenuCheckbox }; 51 | -------------------------------------------------------------------------------- /src/lab-shared/LabNotes.js: -------------------------------------------------------------------------------- 1 | // import * as BABYLON from "babylonjs"; 2 | import * as GUI from "babylonjs-gui"; 3 | import { useRoute } from "vue-router"; 4 | import { watch } from "@vue/runtime-core"; 5 | import { labNotes } from "../composables/LabData"; 6 | // import LabColors from "../lab-shared/LabColors"; 7 | 8 | const createLabNotesPanel = () => { 9 | // const labNotes = ref(""); 10 | const currentRoute = useRoute(); 11 | const title = currentRoute.meta.title; 12 | const subtitle = currentRoute.meta.subtitle; 13 | 14 | // A reference to the BJS GUI Scroll Viewer, too lazy to query this in the graph... 15 | let scrollViewer; 16 | 17 | // A reference to the BJS GUI TextBlock, too lazy to query this in the graph... 18 | let notesText; 19 | 20 | const wrapperPanel = new GUI.StackPanel(); 21 | 22 | const sv = new GUI.ScrollViewer("lab-info-scroll"); 23 | scrollViewer = sv; 24 | 25 | sv.thickness = 12; 26 | sv.color = "#3e4a5d"; 27 | sv.background = "#3e4a5d"; 28 | sv.opacity = 1; 29 | sv.height = `${1024 - 128}px`; 30 | sv.width = `${1024}px`; 31 | sv.barSize = 30; 32 | sv.barColor = "#53637b"; 33 | sv.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 34 | 35 | const panel = new GUI.StackPanel(); 36 | sv.addControl(panel); 37 | 38 | wrapperPanel.addControl(sv); 39 | 40 | const titleTextBlock = new GUI.TextBlock("title-text"); 41 | titleTextBlock.text = title; 42 | titleTextBlock.textWrapping = true; 43 | titleTextBlock.height = "64px"; 44 | titleTextBlock.color = "#d3d9e1"; 45 | titleTextBlock.fontSize = "48px"; 46 | titleTextBlock.fontStyle = "bold"; 47 | titleTextBlock.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 48 | titleTextBlock.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 49 | titleTextBlock.textHorizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 50 | titleTextBlock.textVerticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 51 | panel.addControl(titleTextBlock); 52 | 53 | const subtitleTextBlock = new GUI.TextBlock("subtitle-text"); 54 | subtitleTextBlock.text = subtitle; 55 | subtitleTextBlock.textWrapping = true; 56 | subtitleTextBlock.height = "64px"; 57 | subtitleTextBlock.color = "#d3d9e1"; 58 | subtitleTextBlock.fontSize = "36px"; 59 | subtitleTextBlock.fontStyle = "bold"; 60 | subtitleTextBlock.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 61 | subtitleTextBlock.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 62 | subtitleTextBlock.textHorizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 63 | subtitleTextBlock.textVerticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 64 | panel.addControl(subtitleTextBlock); 65 | 66 | const notesTextBlock = new GUI.TextBlock("logger-text"); 67 | notesText = notesTextBlock; 68 | 69 | notesTextBlock.text = labNotes.value; 70 | notesTextBlock.width = "96%"; 71 | notesTextBlock.height = "2048px"; 72 | notesTextBlock.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 73 | notesTextBlock.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 74 | notesTextBlock.textHorizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 75 | notesTextBlock.textVerticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 76 | notesTextBlock.color = "#d3d9e1"; 77 | notesTextBlock.fontSize = "36px"; 78 | notesTextBlock.textWrapping = true; 79 | 80 | panel.addControl(notesTextBlock); 81 | 82 | watch(labNotes, (newValue) => { 83 | const notes = newValue; 84 | if (scrollViewer && notesText) { 85 | notesText.text = notes; 86 | notesText.resizeToFit = true; 87 | scrollViewer.verticalBar.value = 1; 88 | } 89 | }); 90 | 91 | return wrapperPanel; 92 | }; 93 | 94 | export default createLabNotesPanel; 95 | -------------------------------------------------------------------------------- /src/lab-shared/LabPlayer.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is a shared component used across labs. 3 | Current features 4 | - WebXR default experience 5 | - Teleport system 6 | - Console log common WebXR buttons 7 | - Imports and uses LabConsole for debugging in VR 8 | */ 9 | 10 | // import * as BABYLON from "babylonjs"; 11 | // import { ref, reactive, watch } from "@vue/runtime-core"); 12 | import * as BABYLON from "babylonjs"; 13 | import { createLabMenu } from "../lab-shared/LabMenu"; 14 | import { reactive } from "@vue/runtime-core"; 15 | import { useStorage } from "@vueuse/core"; 16 | import LabColors from "../lab-shared/LabColors"; 17 | 18 | export const createLabPlayer = async (scene, teleportMeshes) => { 19 | // Default settings for the movement controls 20 | const defaultMovementSettings = { 21 | locomotionType: "teleport", // "teleport" or "movement" 22 | // Movement settings 23 | movementEnabled: true, 24 | movementSpeed: 0.5, // 1 is too fast most of the time 25 | rotationEnabled: true, 26 | rotationSpeed: 0.25, 27 | // Teleport settings 28 | parabolicCheckRadius: 5, 29 | rotationAngle: 0.25, // teleport rotation angle, not movement controls 30 | backwardsTeleportationDistance: 0.7 31 | }; 32 | 33 | // Spread the default settings into the stored settings 34 | // This will only be set if the local storage value is not found, else it will use the local storage value 35 | let storedMovementSettings = useStorage("lab-locomotion-config", { 36 | ...defaultMovementSettings 37 | }); 38 | 39 | // Map the stored settings to the reactive settings object 40 | // I could not find a way to get Watch working with useStorage 41 | // I don't actually need this to be reactive, but I'm keeping it for now to match the usage on Lab 026 42 | let movementSettings = reactive({ 43 | locomotionType: storedMovementSettings.value.locomotionType, 44 | movementEnabled: storedMovementSettings.value.movementEnabled, 45 | movementSpeed: storedMovementSettings.value.movementSpeed, 46 | 47 | rotationEnabled: storedMovementSettings.value.rotationEnabled, 48 | rotationSpeed: storedMovementSettings.value.rotationSpeed, 49 | 50 | parabolicCheckRadius: storedMovementSettings.value.parabolicCheckRadius, 51 | rotationAngle: storedMovementSettings.value.rotationAngle, 52 | backwardsTeleportationDistance: storedMovementSettings.value.backwardsTeleportationDistance 53 | }); 54 | 55 | // console.log("storedMovementSettings", storedMovementSettings); 56 | const { toggleMenu } = createLabMenu(scene); 57 | // Create the default experience 58 | let xr = await scene.createDefaultXRExperienceAsync({ 59 | floorMeshes: teleportMeshes, 60 | pointerSelectionOptions: { 61 | enablePointerSelectionOnAllControllers: true 62 | } 63 | }); 64 | 65 | // Move the player when thet enter immersive mode 66 | xr.baseExperience.onInitialXRPoseSetObservable.add((xrCamera) => { 67 | xrCamera.position.z = -2; 68 | }); 69 | 70 | const setupCameraForCollisions = (camera) => { 71 | camera.checkCollisions = true; 72 | camera.applyGravity = true; 73 | camera.ellipsoid = new BABYLON.Vector3(0.7, 1, 0.7); 74 | camera.ellipsoidOffset = new BABYLON.Vector3(0, 0.5, 0); 75 | }; 76 | 77 | const useMovementControls = (featureManager) => { 78 | // Turn off the other feature 79 | featureManager.disableFeature(BABYLON.WebXRFeatureName.TELEPORTATION); 80 | // Configure and enable the movement controls 81 | const swappedHandednessConfiguration = [ 82 | { 83 | allowedComponentTypes: [BABYLON.WebXRControllerComponent.THUMBSTICK_TYPE, BABYLON.WebXRControllerComponent.TOUCHPAD_TYPE], 84 | forceHandedness: "right", 85 | axisChangedHandler: (axes, movementState, featureContext) => { 86 | // console.log(xrInput); 87 | movementState.rotateX = Math.abs(axes.x) > featureContext.rotationThreshold ? axes.x : 0; 88 | movementState.rotateY = Math.abs(axes.y) > featureContext.rotationThreshold ? axes.y : 0; 89 | } 90 | }, 91 | { 92 | allowedComponentTypes: [BABYLON.WebXRControllerComponent.THUMBSTICK_TYPE, BABYLON.WebXRControllerComponent.TOUCHPAD_TYPE], 93 | forceHandedness: "left", 94 | axisChangedHandler: (axes, movementState, featureContext) => { 95 | // console.log(xrInput); 96 | movementState.moveX = Math.abs(axes.x) > featureContext.movementThreshold ? axes.x : 0; 97 | movementState.moveY = Math.abs(axes.y) > featureContext.movementThreshold ? axes.y : 0; 98 | } 99 | } 100 | ]; 101 | 102 | setupCameraForCollisions(xr.input.xrCamera); 103 | 104 | featureManager.enableFeature(BABYLON.WebXRFeatureName.MOVEMENT, "latest", { 105 | xrInput: xr.input, 106 | customRegistrationConfigurations: swappedHandednessConfiguration, 107 | movementEnabled: movementSettings.movementEnabled, 108 | movementSpeed: movementSettings.movementSpeed, 109 | rotationEnabled: movementSettings.rotationEnabled, 110 | rotationSpeed: movementSettings.rotationSpeed 111 | }); 112 | }; 113 | 114 | const useTeleportControls = (featureManager) => { 115 | // Turn off the other feature 116 | featureManager.disableFeature(BABYLON.WebXRFeatureName.MOVEMENT); 117 | // Configure and enable the teleportation feature 118 | const createTeleportationSetup = () => { 119 | const teleportRingMat = new BABYLON.StandardMaterial("grab-mat1", scene); 120 | teleportRingMat.diffuseColor = LabColors["light1"]; 121 | teleportRingMat.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2); 122 | 123 | let setup = { 124 | xrInput: xr.input, 125 | floorMeshes: teleportMeshes 126 | }; 127 | 128 | setup["defaultTargetMeshOptions"] = { 129 | teleportationFillColor: "#3e4a5d", 130 | teleportationBorderColor: "#8854d0", 131 | torusArrowMaterial: teleportRingMat 132 | }; 133 | 134 | setup["renderingGroupId"] = 1; 135 | 136 | return setup; 137 | }; 138 | 139 | const teleportControlManager = featureManager.enableFeature(BABYLON.WebXRFeatureName.TELEPORTATION, "stable", createTeleportationSetup({})); 140 | teleportControlManager.parabolicCheckRadius = movementSettings.parabolicCheckRadius; 141 | teleportControlManager.rotationAngle = movementSettings.rotationAngle; 142 | teleportControlManager.backwardsTeleportationDistance = movementSettings.backwardsTeleportationDistance; 143 | teleportControlManager.rotationEnabled = false; // rotation while teleport is disabled 144 | }; 145 | 146 | const featureManager = xr.baseExperience.featuresManager; 147 | if (movementSettings.locomotionType === "movement") { 148 | useMovementControls(featureManager); 149 | } else { 150 | useTeleportControls(featureManager); 151 | } 152 | 153 | //controller input 154 | xr.input.onControllerAddedObservable.add((controller) => { 155 | controller.onMotionControllerInitObservable.add((motionController) => { 156 | if (motionController.handness === "left") { 157 | const xr_ids = motionController.getComponentIds(); 158 | let triggerComponent = motionController.getComponent(xr_ids[0]); //xr-standard-trigger 159 | triggerComponent?.onButtonStateChangedObservable.add(() => { 160 | if (triggerComponent.pressed) { 161 | console.log("Left Trigger Pressed"); 162 | } 163 | }); 164 | let squeezeComponent = motionController.getComponent(xr_ids[1]); //xr-standard-squeeze 165 | squeezeComponent?.onButtonStateChangedObservable.add(() => { 166 | if (squeezeComponent.pressed) { 167 | console.log("Left Grip Pressed"); 168 | } 169 | }); 170 | let thumbstickComponent = motionController.getComponent(xr_ids[2]); //xr-standard-thumbstick 171 | thumbstickComponent?.onButtonStateChangedObservable.add(() => { 172 | // if (thumbstickComponent.pressed) { 173 | // console.log("Left Thumbstick Pressed"); 174 | // } 175 | }); 176 | // thumbstickComponent.onAxisValueChangedObservable.add((axes) => { 177 | // console.log("Left Axises: " + axes.x + " " + axes.y); 178 | // }); 179 | 180 | let xButtonComponent = motionController.getComponent(xr_ids[3]); //x-button 181 | xButtonComponent?.onButtonStateChangedObservable.add(() => { 182 | if (xButtonComponent.pressed) { 183 | console.log("X Button Pressed"); 184 | } 185 | }); 186 | let yButtonComponent = motionController.getComponent(xr_ids[4]); //y-button 187 | yButtonComponent?.onButtonStateChangedObservable.add(() => { 188 | if (yButtonComponent.pressed) { 189 | console.log("Y Button Pressed"); 190 | toggleMenu(controller); 191 | } 192 | }); 193 | } 194 | 195 | // END LEFT CONTROLLER ------------------------------------------------------------ 196 | 197 | if (motionController.handness === "right") { 198 | const xr_ids = motionController.getComponentIds(); 199 | let triggerComponent = motionController.getComponent(xr_ids[0]); //xr-standard-trigger 200 | triggerComponent?.onButtonStateChangedObservable.add(() => { 201 | if (triggerComponent.pressed) { 202 | console.log("Right Trigger Pressed"); 203 | } 204 | }); 205 | let squeezeComponent = motionController.getComponent(xr_ids[1]); //xr-standard-squeeze 206 | squeezeComponent?.onButtonStateChangedObservable.add(() => { 207 | if (squeezeComponent.pressed) { 208 | console.log("Right Grip Pressed"); 209 | } 210 | }); 211 | let thumbstickComponent = motionController.getComponent(xr_ids[2]); //xr-standard-thumbstick 212 | thumbstickComponent?.onButtonStateChangedObservable.add(() => { 213 | // if (thumbstickComponent.pressed) { 214 | // console.log("Right Thumbstick Pressed"); 215 | // } 216 | }); 217 | // thumbstickComponent.onAxisValueChangedObservable.add((axes) => { 218 | // console.log("Right Axises: " + axes.x + " " + axes.y); 219 | // }); 220 | 221 | let aButtonComponent = motionController.getComponent(xr_ids[3]); //a-button 222 | aButtonComponent?.onButtonStateChangedObservable.add(() => { 223 | if (aButtonComponent.pressed) { 224 | console.log("A Button Pressed"); 225 | } 226 | }); 227 | let bButtonComponent = motionController.getComponent(xr_ids[4]); //b-button 228 | bButtonComponent?.onButtonStateChangedObservable.add(() => { 229 | if (bButtonComponent.pressed) { 230 | console.log("B Button Pressed"); 231 | } 232 | }); 233 | } 234 | }); 235 | }); 236 | 237 | return { xr }; 238 | 239 | // END WebXR -------------------------------------------------- 240 | }; 241 | -------------------------------------------------------------------------------- /src/lab-shared/LabRoom.js: -------------------------------------------------------------------------------- 1 | import * as BABYLON from "babylonjs"; 2 | import * as MAT from "babylonjs-materials"; 3 | import LabColors from "./LabColors"; 4 | 5 | const addLabRoom = (scene) => { 6 | // Add a ground plane to the scene. Used for WebXR teleportation 7 | const ground = BABYLON.MeshBuilder.CreateGround("ground", { height: 20, width: 20, subdivisions: 4 }, scene); 8 | // ground.position.y = 10; 9 | ground.sideOrientation = "DOUBLESIDE"; 10 | const groundMaterial = new MAT.GridMaterial("ground-mat", scene); 11 | groundMaterial.majorUnitFrequency = 5; 12 | groundMaterial.minorUnitFrequency = 0.1; 13 | groundMaterial.gridRatio = 1; 14 | groundMaterial.backFaceCulling = false; 15 | groundMaterial.mainColor = LabColors.light1; 16 | groundMaterial.lineColor = new BABYLON.Color3(1.0, 1.0, 1.0); 17 | groundMaterial.opacity = 0.98; 18 | ground.material = groundMaterial; 19 | ground.checkCollisions = true; 20 | 21 | // Note: the rotation of these elements is set to put the face of the plane/ground facing the inside of the room so that collisions will work. 22 | 23 | const wall1 = BABYLON.MeshBuilder.CreateGround("wall1", { height: 10, width: 20, subdivisions: 4 }, scene); 24 | wall1.rotation = new BABYLON.Vector3(Math.PI / 2, Math.PI, Math.PI / 2); 25 | wall1.position = new BABYLON.Vector3(-10, 5, 0); 26 | wall1.material = groundMaterial; 27 | wall1.sideOrientation = "DOUBLESIDE"; 28 | wall1.checkCollisions = true; 29 | 30 | const wall2 = wall1.clone("wall2"); 31 | wall2.rotation.z = -Math.PI / 2; 32 | wall2.position = new BABYLON.Vector3(10, 5, 0); 33 | 34 | console.log("wall rotation", wall1.rotation, wall2.rotation); 35 | const wall3 = wall1.clone("wall3"); 36 | wall3.rotation = new BABYLON.Vector3(Math.PI / 2, Math.PI / 2, Math.PI / 2); 37 | wall3.position = new BABYLON.Vector3(0, 5, -10); 38 | 39 | const wall4 = wall3.clone("wall4"); 40 | wall4.rotation.z = -Math.PI / 2; 41 | wall4.position = new BABYLON.Vector3(0, 5, 10); 42 | 43 | // Return the ground to use for teleportation 44 | return ground; 45 | }; 46 | export default addLabRoom; 47 | -------------------------------------------------------------------------------- /src/labs/Lab000.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/labs/Lab001.vue: -------------------------------------------------------------------------------- 1 | 94 | 95 | 102 | -------------------------------------------------------------------------------- /src/labs/Lab002.vue: -------------------------------------------------------------------------------- 1 | 148 | 149 | 156 | -------------------------------------------------------------------------------- /src/labs/Lab003.vue: -------------------------------------------------------------------------------- 1 | 192 | 193 | 200 | -------------------------------------------------------------------------------- /src/labs/Lab004.vue: -------------------------------------------------------------------------------- 1 | 257 | 258 | 265 | -------------------------------------------------------------------------------- /src/labs/Lab005.vue: -------------------------------------------------------------------------------- 1 | 127 | 128 | 135 | -------------------------------------------------------------------------------- /src/labs/Lab006.vue: -------------------------------------------------------------------------------- 1 | 124 | 125 | 132 | -------------------------------------------------------------------------------- /src/labs/Lab007.vue: -------------------------------------------------------------------------------- 1 | 192 | 193 | 200 | -------------------------------------------------------------------------------- /src/labs/Lab008.vue: -------------------------------------------------------------------------------- 1 | 120 | 121 | 128 | -------------------------------------------------------------------------------- /src/labs/Lab009.vue: -------------------------------------------------------------------------------- 1 | 124 | 125 | 132 | -------------------------------------------------------------------------------- /src/labs/Lab010.vue: -------------------------------------------------------------------------------- 1 | 333 | 334 | 341 | -------------------------------------------------------------------------------- /src/labs/Lab011.vue: -------------------------------------------------------------------------------- 1 | 76 | 77 | 84 | -------------------------------------------------------------------------------- /src/labs/Lab012.vue: -------------------------------------------------------------------------------- 1 | 274 | 275 | 282 | -------------------------------------------------------------------------------- /src/labs/Lab014.vue: -------------------------------------------------------------------------------- 1 | 126 | 127 | 134 | -------------------------------------------------------------------------------- /src/labs/Lab015.vue: -------------------------------------------------------------------------------- 1 | 264 | 265 | 272 | -------------------------------------------------------------------------------- /src/labs/Lab016.vue: -------------------------------------------------------------------------------- 1 | 155 | 156 | 163 | -------------------------------------------------------------------------------- /src/labs/Lab017.vue: -------------------------------------------------------------------------------- 1 | 139 | 140 | 147 | -------------------------------------------------------------------------------- /src/labs/Lab018.vue: -------------------------------------------------------------------------------- 1 | 259 | 260 | 267 | -------------------------------------------------------------------------------- /src/labs/Lab019.vue: -------------------------------------------------------------------------------- 1 | 114 | 115 | 122 | -------------------------------------------------------------------------------- /src/labs/Lab020.vue: -------------------------------------------------------------------------------- 1 | 151 | 152 | 159 | -------------------------------------------------------------------------------- /src/labs/Lab021.vue: -------------------------------------------------------------------------------- 1 | 179 | 180 | 187 | -------------------------------------------------------------------------------- /src/labs/Lab022.vue: -------------------------------------------------------------------------------- 1 | 177 | 178 | 185 | -------------------------------------------------------------------------------- /src/labs/Lab023.vue: -------------------------------------------------------------------------------- 1 | 178 | 179 | 186 | -------------------------------------------------------------------------------- /src/labs/Lab027.vue: -------------------------------------------------------------------------------- 1 | 147 | 148 | 155 | -------------------------------------------------------------------------------- /src/labs/Lab031.vue: -------------------------------------------------------------------------------- 1 | 102 | 103 | 110 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router"; 4 | import store from "./store"; 5 | 6 | createApp(App).use(store).use(router).mount("#app"); 7 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from "vue-router"; 2 | import Home from "../views/Home.vue"; 3 | import Lab000 from "../labs/Lab000.vue"; 4 | import Lab001 from "../labs/Lab001.vue"; 5 | import Lab002 from "../labs/Lab002.vue"; 6 | import Lab003 from "../labs/Lab003.vue"; 7 | import Lab004 from "../labs/Lab004.vue"; 8 | import Lab005 from "../labs/Lab005.vue"; 9 | import Lab006 from "../labs/Lab006.vue"; 10 | import Lab007 from "../labs/Lab007.vue"; 11 | import Lab008 from "../labs/Lab008.vue"; 12 | import Lab009 from "../labs/Lab009.vue"; 13 | import Lab010 from "../labs/Lab010.vue"; 14 | import Lab011 from "../labs/Lab011.vue"; 15 | import Lab012 from "../labs/Lab012.vue"; 16 | // import Lab013 from "../labs/Lab013.vue"; 17 | import Lab014 from "../labs/Lab014.vue"; 18 | import Lab015 from "../labs/Lab015.vue"; 19 | import Lab016 from "../labs/Lab016.vue"; 20 | import Lab017 from "../labs/Lab017.vue"; 21 | import Lab018 from "../labs/Lab018.vue"; 22 | import Lab019 from "../labs/Lab019.vue"; 23 | import Lab020 from "../labs/Lab020.vue"; 24 | import Lab021 from "../labs/Lab021.vue"; 25 | import Lab022 from "../labs/Lab022.vue"; 26 | import Lab023 from "../labs/Lab023.vue"; 27 | import Lab024 from "../labs/Lab024.vue"; 28 | import Lab025 from "../labs/Lab025.vue"; 29 | import Lab026 from "../labs/Lab026.vue"; 30 | import Lab027 from "../labs/Lab027.vue"; 31 | import Lab028 from "../labs/Lab028.vue"; 32 | import Lab029 from "../labs/Lab029.vue"; 33 | import Lab030 from "../labs/Lab030.vue"; 34 | import Lab031 from "../labs/Lab031.vue"; 35 | import Lab032 from "../labs/Lab032.vue"; 36 | 37 | const routes = [ 38 | { 39 | path: "/", 40 | name: "Overview", 41 | component: Home 42 | }, 43 | { 44 | path: "/Lab000", 45 | name: "Lab000", 46 | component: Lab000, 47 | meta: { 48 | title: "Lab 000", 49 | subtitle: "Hello Canvatorium", 50 | status: "complete" 51 | } 52 | }, 53 | { 54 | path: "/Lab001", 55 | name: "Lab001", 56 | component: Lab001, 57 | meta: { 58 | title: "Lab 001", 59 | subtitle: "Picking Colors", 60 | status: "complete" 61 | } 62 | }, 63 | 64 | { 65 | path: "/Lab002", 66 | name: "Lab002", 67 | component: Lab002, 68 | meta: { 69 | title: "Lab 002", 70 | subtitle: "watch() / watchEffect()", 71 | status: "complete" 72 | } 73 | }, 74 | { 75 | path: "/Lab003", 76 | name: "Lab003", 77 | component: Lab003, 78 | meta: { 79 | title: "Lab 003", 80 | subtitle: "Default XR Experience", 81 | status: "complete" 82 | } 83 | }, 84 | { 85 | path: "/Lab004", 86 | name: "Lab004", 87 | component: Lab004, 88 | meta: { 89 | title: "Lab 004", 90 | subtitle: "XR Controller Buttons", 91 | status: "complete" 92 | } 93 | }, 94 | { 95 | path: "/Lab005", 96 | name: "Lab005", 97 | component: Lab005, 98 | meta: { 99 | title: "Lab 005", 100 | subtitle: "Router Nav to Lab 006", 101 | status: "failed" 102 | } 103 | }, 104 | { 105 | path: "/Lab006", 106 | name: "Lab006", 107 | component: Lab006, 108 | meta: { 109 | title: "Lab 006", 110 | subtitle: "Router Nav to Lab 005", 111 | status: "failed" 112 | } 113 | }, 114 | { 115 | path: "/Lab007", 116 | name: "Lab007", 117 | component: Lab007, 118 | meta: { 119 | title: "Lab 007", 120 | subtitle: "Console... Log(?)", 121 | status: "complete" 122 | } 123 | }, 124 | { 125 | path: "/Lab008", 126 | name: "Lab008", 127 | component: Lab008, 128 | meta: { 129 | title: "Lab 008", 130 | subtitle: "Near Menu", 131 | status: "complete" 132 | } 133 | }, 134 | { 135 | path: "/Lab009", 136 | name: "Lab009", 137 | component: Lab009, 138 | meta: { 139 | title: "Lab 009", 140 | subtitle: "Title Card & Vue 3 Composables", 141 | status: "complete" 142 | } 143 | }, 144 | { 145 | path: "/Lab010", 146 | name: "Lab010", 147 | component: Lab010, 148 | meta: { 149 | title: "Lab 010", 150 | subtitle: "Grab Lab", 151 | status: "complete" 152 | } 153 | }, 154 | { 155 | path: "/Lab011", 156 | name: "Lab011", 157 | component: Lab011, 158 | meta: { 159 | title: "Lab 011", 160 | subtitle: "Shared Lab Player", 161 | status: "complete" 162 | } 163 | }, 164 | { 165 | path: "/Lab012", 166 | name: "Lab012", 167 | component: Lab012, 168 | meta: { 169 | title: "Lab 012", 170 | subtitle: "Gizmos!", 171 | status: "complete" 172 | } 173 | }, 174 | // { 175 | // path: "/Lab013", 176 | // name: "Lab013", 177 | // component: Lab013, 178 | // meta: { 179 | // title: "Lab 013", 180 | // subtitle: "Nothing to see here. Move along.", 181 | // status: "running" 182 | // } 183 | // }, 184 | { 185 | path: "/Lab014", 186 | name: "Lab014", 187 | component: Lab014, 188 | meta: { 189 | title: "Lab 014", 190 | subtitle: "Follow Behaviors", 191 | status: "complete" 192 | } 193 | }, 194 | { 195 | path: "/Lab015", 196 | name: "Lab015", 197 | component: Lab015, 198 | meta: { 199 | title: "Lab 015", 200 | subtitle: "Resizable GUI Cards", 201 | status: "complete" 202 | } 203 | }, 204 | { 205 | path: "/Lab016", 206 | name: "Lab016", 207 | component: Lab016, 208 | meta: { 209 | title: "Lab 016", 210 | subtitle: "Snapping and History", 211 | status: "complete" 212 | } 213 | }, 214 | { 215 | path: "/Lab017", 216 | name: "Lab017", 217 | component: Lab017, 218 | meta: { 219 | title: "Lab 017", 220 | subtitle: "Surface Magnetism Behavior", 221 | status: "complete" 222 | } 223 | }, 224 | { 225 | path: "/Lab018", 226 | name: "Lab018", 227 | component: Lab018, 228 | meta: { 229 | title: "Lab 018", 230 | subtitle: "Intro to Actions", 231 | status: "complete" 232 | } 233 | }, 234 | { 235 | path: "/Lab019", 236 | name: "Lab019", 237 | component: Lab019, 238 | meta: { 239 | title: "Lab 019", 240 | subtitle: "Action Triggers in WebXR", 241 | status: "complete" 242 | } 243 | }, 244 | { 245 | path: "/Lab020", 246 | name: "Lab020", 247 | component: Lab020, 248 | meta: { 249 | title: "Lab 020", 250 | subtitle: "Working with Assets", 251 | status: "complete" 252 | } 253 | }, 254 | { 255 | path: "/Lab021", 256 | name: "Lab021", 257 | component: Lab021, 258 | meta: { 259 | title: "Lab 021", 260 | subtitle: "Dynamic Texture Cards", 261 | status: "complete" 262 | } 263 | }, 264 | { 265 | path: "/Lab022", 266 | name: "Lab022", 267 | component: Lab022, 268 | meta: { 269 | title: "Lab 022", 270 | subtitle: "3D Cards pop out from 2D GUI", 271 | status: "complete" 272 | } 273 | }, 274 | { 275 | path: "/Lab023", 276 | name: "Lab023", 277 | component: Lab023, 278 | meta: { 279 | title: "Lab 023", 280 | subtitle: "Save Object Transform", 281 | status: "complete" 282 | } 283 | }, 284 | { 285 | path: "/Lab024", 286 | name: "Lab024", 287 | component: Lab024, 288 | meta: { 289 | title: "Lab 024", 290 | subtitle: "Movement Controls", 291 | status: "running" 292 | } 293 | }, 294 | { 295 | path: "/Lab025", 296 | name: "Lab025", 297 | component: Lab025, 298 | meta: { 299 | title: "Lab 025", 300 | subtitle: "Teleport Controls", 301 | status: "running" 302 | } 303 | }, 304 | { 305 | path: "/Lab026", 306 | name: "Lab026", 307 | component: Lab026, 308 | meta: { 309 | title: "Lab 026", 310 | subtitle: "User Locomotion Settings", 311 | status: "running" 312 | } 313 | }, 314 | { 315 | path: "/Lab027", 316 | name: "Lab027", 317 | component: Lab027, 318 | meta: { 319 | title: "Lab 027", 320 | subtitle: "Performance Testing", 321 | status: "running" 322 | } 323 | }, 324 | { 325 | path: "/Lab028", 326 | name: "Lab028", 327 | component: Lab028, 328 | meta: { 329 | title: "Lab 028", 330 | subtitle: "Stoa (Geometry)", 331 | status: "running" 332 | } 333 | }, 334 | { 335 | path: "/Lab029", 336 | name: "Lab029", 337 | component: Lab029, 338 | meta: { 339 | title: "Lab 029", 340 | subtitle: "Stoa (Lighting & Textures)", 341 | status: "running" 342 | } 343 | }, 344 | { 345 | path: "/Lab030", 346 | name: "Lab030", 347 | component: Lab030, 348 | meta: { 349 | title: "Lab 030", 350 | subtitle: "Stoa (Water)", 351 | status: "failed" 352 | } 353 | }, 354 | { 355 | path: "/Lab031", 356 | name: "Lab031", 357 | component: Lab031, 358 | meta: { 359 | title: "Lab 031", 360 | subtitle: "Playing with Images", 361 | status: "running" 362 | } 363 | }, 364 | { 365 | path: "/Lab032", 366 | name: "Lab032", 367 | component: Lab032, 368 | meta: { 369 | title: "Lab 032", 370 | subtitle: "VR Lathe Array", 371 | status: "running" 372 | } 373 | } 374 | ]; 375 | 376 | const router = createRouter({ 377 | history: createWebHistory(), 378 | routes 379 | }); 380 | 381 | export default router; 382 | -------------------------------------------------------------------------------- /src/scenes/Lab000Wrapper.js: -------------------------------------------------------------------------------- 1 | import * as BABYLON from "babylonjs"; 2 | import * as GUI from "babylonjs-gui"; 3 | 4 | import addLabCamera from "../lab-shared/LabCamera"; 5 | import addLabLights from "../lab-shared/LabLights"; 6 | import addLabRoom from "../lab-shared/LabRoom"; 7 | import { createLabPlayer } from "../lab-shared/LabPlayer"; 8 | 9 | const Lab000Wrapper = { 10 | engine: null, 11 | scene: null, 12 | 13 | createScene: async (canvas) => { 14 | // Create and customize the scene 15 | const engine = new BABYLON.Engine(canvas); 16 | const scene = new BABYLON.Scene(engine); 17 | 18 | // Use the shared lab tools 19 | addLabCamera(canvas, scene); 20 | scene.getCameraByName("camera").position = new BABYLON.Vector3(0, 0.8, -4); 21 | addLabLights(scene); 22 | const ground = addLabRoom(scene); 23 | 24 | // Make some boxes to test out the colors in VR 25 | const group = new BABYLON.Mesh("logo-group"); 26 | group.position = new BABYLON.Vector3(-3.5, 0.5, 0); 27 | 28 | makeCard(); 29 | 30 | // Use the LabPlayer 31 | const { xr } = await createLabPlayer(scene, [ground]); 32 | console.log(xr); 33 | 34 | engine.runRenderLoop(() => { 35 | scene.render(); 36 | }); 37 | } 38 | 39 | // --- Call these functions from Vue to pass in the data or setup the scene --- 40 | }; 41 | 42 | const makeCard = () => { 43 | // GUI 44 | var plane = BABYLON.MeshBuilder.CreatePlane("plane"); 45 | plane.position.y = 1; 46 | 47 | var advancedTexture = GUI.AdvancedDynamicTexture.CreateForMesh(plane); 48 | advancedTexture.name = "card-texture"; 49 | 50 | var cardText = new GUI.TextBlock("card-text"); 51 | cardText.text = "Canvatorium"; 52 | cardText.color = "white"; 53 | cardText.fontSize = 64; 54 | 55 | advancedTexture.addControl(cardText); 56 | plane.scaling = new BABYLON.Vector3(5, 5, 5); 57 | }; 58 | 59 | export default Lab000Wrapper; 60 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from "vuex"; 2 | 3 | export default createStore({ 4 | state: {}, 5 | mutations: {}, 6 | actions: {}, 7 | modules: {}, 8 | }); 9 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 78 | 79 | 85 | 86 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | module.exports = { 4 | devServer: { 5 | open: process.platform === "darwin", 6 | host: "0.0.0.0", 7 | port: 8080, 8 | https: { 9 | key: fs.readFileSync(".certs/key.pem"), 10 | cert: fs.readFileSync(".certs/cert.pem") 11 | }, 12 | hotOnly: false 13 | } 14 | }; 15 | --------------------------------------------------------------------------------