├── .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 |
22 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
23 |
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 |
2 |
3 |
No Scene
4 |
5 |
6 | {{ this.$route?.meta?.title }} - {{ this.$route?.meta?.subtitle }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/SidebarNav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ item?.meta?.title || item?.name }}
5 |
6 | - {{ item?.meta?.subtitle || "" }}
8 | X
9 |
10 |
11 |
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 |
2 |
3 |
4 | Lab 000 - Hello Canvatorium
5 | Just setting up the shared lab resources. Grid, Lights, Camera, etc.
6 |
7 |
8 |
9 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/labs/Lab001.vue:
--------------------------------------------------------------------------------
1 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/src/labs/Lab002.vue:
--------------------------------------------------------------------------------
1 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/src/labs/Lab003.vue:
--------------------------------------------------------------------------------
1 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
--------------------------------------------------------------------------------
/src/labs/Lab004.vue:
--------------------------------------------------------------------------------
1 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
--------------------------------------------------------------------------------
/src/labs/Lab005.vue:
--------------------------------------------------------------------------------
1 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/src/labs/Lab006.vue:
--------------------------------------------------------------------------------
1 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/src/labs/Lab007.vue:
--------------------------------------------------------------------------------
1 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
--------------------------------------------------------------------------------
/src/labs/Lab008.vue:
--------------------------------------------------------------------------------
1 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/src/labs/Lab009.vue:
--------------------------------------------------------------------------------
1 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/src/labs/Lab010.vue:
--------------------------------------------------------------------------------
1 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
--------------------------------------------------------------------------------
/src/labs/Lab011.vue:
--------------------------------------------------------------------------------
1 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/labs/Lab012.vue:
--------------------------------------------------------------------------------
1 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
--------------------------------------------------------------------------------
/src/labs/Lab014.vue:
--------------------------------------------------------------------------------
1 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
--------------------------------------------------------------------------------
/src/labs/Lab015.vue:
--------------------------------------------------------------------------------
1 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
--------------------------------------------------------------------------------
/src/labs/Lab016.vue:
--------------------------------------------------------------------------------
1 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
--------------------------------------------------------------------------------
/src/labs/Lab017.vue:
--------------------------------------------------------------------------------
1 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
--------------------------------------------------------------------------------
/src/labs/Lab018.vue:
--------------------------------------------------------------------------------
1 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
--------------------------------------------------------------------------------
/src/labs/Lab019.vue:
--------------------------------------------------------------------------------
1 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/src/labs/Lab020.vue:
--------------------------------------------------------------------------------
1 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/src/labs/Lab021.vue:
--------------------------------------------------------------------------------
1 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
--------------------------------------------------------------------------------
/src/labs/Lab022.vue:
--------------------------------------------------------------------------------
1 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
--------------------------------------------------------------------------------
/src/labs/Lab023.vue:
--------------------------------------------------------------------------------
1 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
--------------------------------------------------------------------------------
/src/labs/Lab027.vue:
--------------------------------------------------------------------------------
1 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/src/labs/Lab031.vue:
--------------------------------------------------------------------------------
1 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
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 |
2 |
3 |
This is an about page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Get Started
5 |
6 |
7 |
8 | Visit Lab 026 to customize
9 | your Locomotion Preferences.
10 |
11 | These preferences will be saved to Local Storage in your browser and
12 | will be used in most other Labs.
13 |
14 |
15 | Use the sidebar to select a Lab. "Active" labs are the ones I'm
16 | working on.
17 |
18 |
19 | Use the VR button to try the lab on a headset. Most labs are made for
20 | VR amd may not make sense outside of VR.
21 |
22 |
23 | Toggle the Lab Menu (Oculus Y button) to view Lab Notes and other menu
24 | options.
25 |
26 |
27 | The Lab Menu contains notes about the lab and a simple version of
28 | console logging.
29 |
30 |
31 | A few notes
32 | Everything about this project is a work in progress.
33 |
34 | This loosely structured project is a place where I can prototype and
35 | experiment with UI/UX for spatial interfaces. Some of my ideas include:
36 |
37 |
38 |
39 |
40 | Using Babylon JS with the Vue 3 Composition API and script setup
41 |
42 | Reactive data on Babylon JS GUI
43 | Reactive/interactive controls with Babylon JS GUI
44 |
45 | Laying out data in 3D Building controls for common data editing tasks
46 |
47 |
48 | Navigating data as if each record were an object in 3D Exploring
49 | productive workflows
50 |
51 |
52 | Replacing some of the flat-screen work that I do in various apps and
53 | sites
54 |
55 | Working with various APIs and databases
56 | Detailing and documenting common Babylon GUI workflows
57 |
58 |
59 |
60 | The unit of account for this project is the lab . Each lab is a
61 | place where I can explore a concept or idea. In Vue terms, each lab will
62 | be a page or component that we can navigate to. I won’t write a post for
63 | every lab.
64 |
65 | Each lab contains a Babylon JS scene, a title, and some notes.
66 |
67 | Check the code for each lab for more details. The repo is
68 | here
69 |
70 |
71 | I have a GitHub Project
72 | Board
73 | setup that I use to track the status of each lab and idea.
74 |
75 |
76 |
77 |
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 |
--------------------------------------------------------------------------------