15 |
16 |
17 |
18 |
19 |
20 | hello world!
21 |
22 |
23 |
24 |
25 | Building for Mixed Reality, in Mixed Reality.
26 |
27 |
28 | Ex veniam sunt laboris magna commodo minim eiusmod consequat dolore velit ipsum dolore. Sit nisi id tempor occaecat aute commodo nulla fugiat eu ex sit anim duis. Non eiusmod dolore exercitation adipisicing laboris elit commodo enim proident et.
29 |
30 | Eiusmod tempor veniam et nulla eiusmod ex aute excepteur enim. Velit voluptate proident fugiat aute ea. Qui proident ad consequat enim anim nulla. Est aliquip sint nostrud elit pariatur ipsum dolor magna est eiusmod Lorem pariatur labore. Adipisicing commodo occaecat ea ut nulla magna irure officia nostrud aliqua officia culpa ex pariatur. Aliquip consequat ad proident elit laboris et aliqua nisi est amet tempor. Dolor esse labore laboris reprehenderit id sit.
31 |
32 | Anim ipsum sit dolore id excepteur ex aliqua aute culpa duis adipisicing. Anim magna minim do nisi anim amet pariatur. Nulla ad ullamco labore culpa quis. Incididunt pariatur laboris do duis irure.
33 |
34 | Pariatur excepteur et proident nisi. Id exercitation qui pariatur aliquip eiusmod. Nulla pariatur ad excepteur aliqua dolore sint laboris sit laborum adipisicing voluptate.
35 |
36 | Sit aliqua eu ullamco culpa labore nostrud sit. Ullamco sit cillum excepteur officia irure laboris occaecat. Esse anim ut voluptate excepteur excepteur nostrud laboris.
37 |
38 | Voluptate amet exercitation in consequat adipisicing eu in ea nulla eu occaecat excepteur ea irure. Nisi enim mollit do proident ex. In exercitation nostrud anim ad nulla dolor aute Lorem ipsum nostrud adipisicing pariatur. Deserunt deserunt amet anim cillum. Ex nostrud eu aute eu amet dolor. Ex aute in quis qui irure ut pariatur nisi in. Proident id fugiat anim excepteur cupidatat duis cupidatat adipisicing reprehenderit.
39 |
40 | Sint eiusmod laboris eu voluptate ullamco ea fugiat exercitation id ad magna commodo. Irure proident et qui ullamco aliquip aute dolor nostrud laborum excepteur sint aute ea. Sit consectetur qui Lorem esse magna voluptate irure aliqua nostrud adipisicing eu irure cupidatat sunt. Officia dolore sint laboris Lorem enim labore mollit reprehenderit laboris amet. Aute eiusmod fugiat labore veniam cupidatat dolor ipsum.
41 |
42 | Sit aliquip excepteur laboris ullamco minim sint sit dolor cupidatat culpa consequat quis sit tempor. Culpa voluptate eiusmod et eiusmod. Lorem deserunt ullamco laborum ut. Cillum aute dolore mollit sint amet. Qui ea deserunt consectetur eiusmod. Ullamco qui irure nostrud ea est do. Pariatur laboris labore in irure ea voluptate quis.
43 |
44 | Nostrud deserunt ad officia ut irure cupidatat pariatur ea fugiat. Cillum nisi ullamco tempor consequat laborum quis pariatur cillum enim in elit veniam. Cupidatat laborum do cupidatat irure occaecat sunt nostrud. Adipisicing cillum non adipisicing enim officia excepteur ullamco fugiat.
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
61 |
62 |
--------------------------------------------------------------------------------
/samples/examples/images.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | mr.js
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | volumetrics
17 |
18 |
24 |
25 |
26 | spin
27 |
28 |
29 |
30 |
31 | mr.js
32 |
33 |
34 |
35 |
36 |
37 | Fill
38 |
39 |
40 |
41 |
42 |
43 | Contain
44 |
45 |
46 |
47 |
48 |
49 | Scale-down
50 |
51 |
52 |
53 |
54 |
55 | Cover
56 |
57 |
58 |
59 |
60 |
61 | None (original scaling)
62 |
63 |
64 |
65 |
66 |
67 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
96 |
97 |
--------------------------------------------------------------------------------
/samples/examples/skybox-style.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Roboto';
3 | src: url('/examples-assets/fonts/Roboto-Regular.ttf') format('truetype');
4 | font-weight: 100 900;
5 | }
6 |
7 | @font-face {
8 | font-family: 'Bricolage';
9 | src: url('/examples-assets/fonts/BricolageGrotesque.ttf') format('truetype');
10 | font-weight: 100 900;
11 | }
12 |
13 |
14 | :root {
15 | --font-title: "Bricolage";
16 | /* --font-body: "Onest", system-ui, sans-serif; */
17 | /* PADDINGS & MARGINS */
18 | --space: 4rem;
19 | --outer: 1.5rem;
20 | --inter: 1rem;
21 | --inner: 0.5rem;
22 | --inlet: 0.25rem;
23 | /* CONSTRAINTS */
24 | --hwdth: 56rem;
25 | --mwdth: 68rem;
26 | /* RADIUS */
27 | --border-radius-large: 2rem;
28 | /* SCROLL */
29 | --scroll: 0;
30 | --position: 0;
31 | /* NEUTRAL COLORS */
32 | --neutral-hue: 40;
33 | --color-title: hsl(var(--neutral-hue), 20%, 20%);
34 | --color-text: hsl(var(--neutral-hue), 15%, 10%);
35 | --color-text-light: hsl(var(--neutral-hue), 20%, 42%);
36 | --color-paper: hsl(var(--neutral-hue), 80%, 94%);
37 | --color-paper-light: hsl(var(--neutral-hue), 75%, 92%);
38 | --color-paper-card: white;
39 | /* BUTTONS */
40 | /* --rythm-hue: 310; */
41 | --rythm-hue: 300;
42 | /* --rythm-hue: 40; */
43 | --color-button: hsl(var(--rythm-hue), 15%, 96%);
44 | --color-button-hover: hsl(var(--rythm-hue), 100%, 90%);
45 | --color-button-active: hsl(var(--rythm-hue), 100%, 85%);
46 | --color-button-text: hsl(var(--rythm-hue), 15%, 15%);
47 | --color-button-ring: hsl(var(--rythm-hue), 100%, 70%);
48 | /* CTA */
49 | /* --hue: 210; */
50 | --hue: 210;
51 | --color-cta: hsl(var(--hue), 70%, 40%);
52 | --color-cta-hover: hsl(var(--hue), 70%, 55%);
53 | --color-cta-active: hsl(var(--hue), 70%, 60%);
54 | --color-cta-text: hsl(var(--hue), 10%, 100%);
55 | --color-cta-focus-ring: hsl(var(--hue), 100%, 100%);
56 | /* OPTIONS */
57 | --option-hue: 40;
58 | --color-option: hsl(var(--option-hue), 50%, 83%);
59 | --color-option-hover: hsl(var(--option-hue), 50%, 86%);
60 | --color-option-active: hsl(var(--option-hue), 50%, 88%);
61 | --color-option-text: var(--color-text);
62 | --color-option-focus-ring: hsl(var(--option-hue), 50%, 50%);
63 | /* OPTIONS SELECTED */
64 | --color-option-selected: hsl(var(--option-hue), 100%, 100%);
65 | --color-option-selected-text: hsl(var(--hue), 80%, 30%);
66 | /* FOCUS */
67 | --color-focus-outline: hsl(var(--hue), 100%, 50%);
68 | /* BORDERS */
69 | --color-border-light: hsl(var(--rythm-hue), 15%, 92%);
70 | --color-border-static: hsla(var(--rythm-hue), 100%, 15%, 15%);
71 | --color-border-field-hover: hsl(var(--rythm-hue), 80%, 80%);
72 | --color-border-field-active: hsl(var(--rythm-hue), 100%, 70%);
73 | /* SHADOWS */
74 | --button-shadow: 0 0.25rem 1rem -0.5rem hsla(30, 15%, 15%, 20%);
75 | --main-shadow: 0 0 1rem -0.5rem hsla(30, 50%, 25%, 50%);
76 | }
77 |
78 | mr-text {
79 | font-family: 'Roboto';
80 | line-height: 100%;
81 | font-size: 16px;
82 | }
83 |
84 | .mrjs {
85 | font-family: var(--font-title);
86 | font-size: 4vw;
87 | line-height: 100%;
88 | color: rgb(255, 255, 255);
89 | grid-column: 1;
90 | }
91 |
92 | .company {
93 | font-family: var(--font-title);
94 | font-size: 4vw;
95 | line-height: 100%;
96 | color: rgb(255, 255, 255);
97 | align-self: center;
98 | grid-column: 4;
99 | }
100 |
101 | .logo {
102 | grid-column: 3;
103 | z-index: 25;
104 | }
105 |
106 | #logo-model{
107 | scale: 0.0005;
108 | }
109 |
110 |
111 | .layout {
112 | background-color: var(--color-paper);
113 | padding-top: 6vw;
114 | display: grid;
115 | grid-template-columns: 1fr 2fr 1fr;
116 | gap: 1vw;
117 | grid-auto-rows: auto;
118 | }
119 |
120 | .col-1{
121 | grid-column: 1;
122 | }
123 |
124 | .col-2{
125 | grid-column: 2;
126 | }
127 |
128 | .col-3{
129 | grid-column: 3;
130 | }
131 |
132 | #navbar {
133 | background-color: #141414;
134 | border-radius: 1%;
135 | position:fixed;
136 | top: 0;
137 | left: 0;
138 | width: 100%;
139 | z-index: 4;
140 | display: grid;
141 | grid-template-columns: 1fr 2fr 0.5fr 0.5fr;
142 | padding: 10px;
143 | }
144 |
145 | .title {
146 | margin: 0 auto;
147 | font-family: var(--font-title);
148 | font-weight: 500;
149 | font-size: 8vw;
150 | line-height: 100%;
151 | color: rgba(24, 24, 24, 0.75);
152 | font-weight: bold;
153 | align-self: center;
154 |
155 | }
156 |
157 | .title-2 {
158 | font-family: var(--font-title);
159 | font-size: 5vw;
160 | margin-bottom: 1vh;
161 | line-height: 100%;
162 | color: rgba(24, 24, 24, 0.75);
163 | align-self: center;
164 | margin: 0 auto;
165 | }
166 | .subtitle {
167 | font-family: var(--font-title);
168 | font-size: 3vw;
169 | margin-bottom: 1vh;
170 | line-height: 100%;
171 | color: rgba(24, 24, 24, 0.75);
172 | }
173 |
174 | .label {
175 | font-family: var(--font-title);
176 | font-size: 2vw;
177 | line-height: 100%;
178 | color: rgba(24, 24, 24, 0.75);
179 | }
180 |
181 | .label-2 {
182 | font-family: var(--font-title);
183 | font-size: 1.5vw;
184 | line-height: 100%;
185 | color: rgba(24, 24, 24, 0.75);
186 | }
187 |
188 | .button {
189 | padding: 10px;
190 | margin: 10px;
191 | width: fit-content;
192 | }
193 |
--------------------------------------------------------------------------------
/samples/examples/text.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | mr.js - text
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | mrjs
17 |
18 |
25 |
26 |
27 | volumetrics
28 |
29 |
30 |
31 |
32 | Text and TextInputs
33 |
34 |
35 |
36 | Text runs the internet, hence it being very important when it comes to web-library creation. It includes features such as readable text and input fields for user interaction.
37 |
38 |
39 |
40 | Examples
41 |
42 |
43 |
44 | mr-text
45 |
46 |
47 |
48 | Hi hello I'm a bunch of text. I'm on one line with no breaks
49 |
50 | Im on another line now.
51 |
52 |
53 |
54 | mr-text that should be `.innerText` updated by js
55 |
56 |
57 |
58 | Before I said this
59 |
60 |
61 | Before I said this
62 |
63 |
64 |
65 | mr-textarea
66 |
67 | Try typing in the below...
68 |
69 |
70 |
71 |
72 | mr-textfield
73 |
74 | Try typing in the below...
75 |
76 |
77 |
78 |
79 | Text
80 |
81 |
82 |
83 | Written pure text is achieved using the text component, `mr-text` with the text written within the tags (<...>words go here<.../>).
84 |
85 |
86 |
87 | TextArea
88 |
89 |
90 |
91 | TextArea is a multi-line input version of `mr-text`, extending from the MRTextInput class. It is achieved using the `mr-textarea` component and follows the usual textarea attributes.
92 |
93 |
94 |
95 | TextField
96 |
97 |
98 |
99 | TextField is a one-line input version of `mr-text`, extending from the MRTextInput class. It is achieved using the `mr-textfield` component and mimics the general 'input' html element.
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
114 |
115 |
--------------------------------------------------------------------------------
/samples/examples/video.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | mr.js - video
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Play
16 | Stop
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
52 |
53 |
--------------------------------------------------------------------------------
/samples/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/samples/index-assets/BricolageGrotesque.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/BricolageGrotesque.ttf
--------------------------------------------------------------------------------
/samples/index-assets/BricolageGrotesque96pt-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/BricolageGrotesque96pt-SemiBold.ttf
--------------------------------------------------------------------------------
/samples/index-assets/Onest-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/Onest-Bold.ttf
--------------------------------------------------------------------------------
/samples/index-assets/Onest-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/Onest-Regular.ttf
--------------------------------------------------------------------------------
/samples/index-assets/all-in-one.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/all-in-one.glb
--------------------------------------------------------------------------------
/samples/index-assets/bowtie.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/bowtie.glb
--------------------------------------------------------------------------------
/samples/index-assets/familiar.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/familiar.glb
--------------------------------------------------------------------------------
/samples/index-assets/jigsaw.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/jigsaw.glb
--------------------------------------------------------------------------------
/samples/index-assets/koifish.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/koifish.glb
--------------------------------------------------------------------------------
/samples/index-assets/mrjs_logo.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/mrjs_logo.glb
--------------------------------------------------------------------------------
/samples/index-assets/mrjs_logo_large.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/mrjs_logo_large.glb
--------------------------------------------------------------------------------
/samples/index-assets/puzzle.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/index-assets/puzzle.glb
--------------------------------------------------------------------------------
/samples/index-style.css:
--------------------------------------------------------------------------------
1 | /* THIS IS AUTO GENERATED FROM scripts/update-all-submodules.sh ANY DIRECT EDITS WILL NOT BE SAVED */
2 | @font-face {
3 | font-family: 'Onest';
4 | src: url('./index-assets/Onest-Regular.ttf') format('truetype');
5 | font-weight: 400;
6 | }
7 |
8 | @font-face {
9 | font-family: 'Bricolage';
10 | src: url('./index-assets/BricolageGrotesque96pt-SemiBold.ttf') format('truetype');
11 | font-weight: 700;
12 | }
13 |
14 | :root {
15 | --font-head: "Bricolage";
16 | --font-body: "Onest";
17 |
18 | --ink: #242424;
19 | --paper: white;
20 | --accent: aquamarine;
21 | --nav: #111;
22 |
23 | --space-around: 3vw;
24 | --space-between: 60px;
25 | --space-vertical: 12px;
26 | --height: 48px;
27 | --max-width: 600px;
28 | --max-width-h1: 840px;
29 | }
30 |
31 | html,
32 | body, mr-app, mr-panel {
33 | padding: 0;
34 | margin: 0;
35 | border: none;
36 | box-sizing: inherit;
37 | }
38 |
39 | mr-app {
40 | font-family: 'Onest';
41 | line-height: 100%;
42 | font-size: 18px;
43 | }
44 |
45 | .nav {
46 | position: fixed;
47 | display: flex;
48 | flex-direction: row;
49 | top: var(--space-around);
50 | left: var(--space-around);
51 | width: calc(100% - var(--space-around) - var(--space-around));
52 | height: var(--height);
53 | background-color: var(--nav);
54 | align-items: center;
55 | justify-content: center;
56 | padding: 0px 8px;
57 | border-radius: 24px;
58 | }
59 |
60 | mr-panel {
61 | display: flex;
62 | flex-flow: column nowrap;
63 | align-items: start;
64 | gap: var(--space-between);
65 | padding: var(--space-around) 3vw;
66 | background-color: var(--paper);
67 | }
68 |
69 | .section {
70 | display: flex;
71 | flex-direction: column;
72 | /* align-items: start; */
73 | /* justify-content: center; */
74 | letter-spacing: 1px;
75 | /* line-height: 1.25; */
76 | line-height: 1.25;
77 | width: 100%;
78 | height: 100%;
79 | }
80 |
81 | .gapped {
82 | gap: var(--space-vertical);
83 | }
84 |
85 | .padded {
86 | padding: var(--space-around);
87 | }
88 |
89 | .narrow {
90 | max-width: 300px;
91 | }
92 |
93 | .diptych {
94 | display: grid;
95 | width: 100%;
96 | grid-template-columns: 1fr 1fr;
97 | /* grid-gap: 10px; */
98 | justify-content: center;
99 | align-items: center;
100 | }
101 |
102 | .triptych {
103 | display: grid;
104 | width: 100%;
105 | grid-template-columns: 1fr 1fr 1fr;
106 | justify-content: start;
107 | align-items: start;
108 | }
109 |
110 | .illustration {
111 | display: flex;
112 | flex-flow: column nowrap;
113 | align-items: center;
114 | justify-content: center;
115 | width: 100%;
116 | height: 100%;
117 | min-height: 300px;
118 | background-color: var(--accent);
119 | border-radius: 10px;
120 | padding: 10px;
121 | }
122 |
123 | .documentation {
124 | background-color: var(--accent);
125 | width: fit-content;
126 | }
127 |
128 | .example-link {
129 | font-size: 2rem;
130 | }
131 |
132 | mr-text {
133 | letter-spacing: 1px;
134 | line-height: 100%;
135 | color: var(--ink);
136 | }
137 |
138 | .chapeau {
139 | font-size: 22px;
140 | max-width: 760px;
141 | }
142 |
143 | .h1 {
144 | max-width: var(--max-width-h1);
145 | font-family: "Bricolage";
146 | font-size: 44px;
147 | font-weight: 700;
148 | padding-bottom: 20px;
149 | }
150 |
151 | .h2 {
152 | font-family: "Bricolage";
153 | font-size: 32px;
154 | font-weight: 700;
155 | }
156 |
157 | .h3 {
158 | font-family: "Bricolage";
159 | font-size: 24px;
160 | font-weight: 700;
161 | }
162 |
163 | .mosaic {
164 | display: grid;
165 | width: 100%;
166 | grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
167 | gap: var(--space-around);
168 | justify-items: center;
169 | align-items: center;
170 | }
171 |
172 | mr-button {
173 | padding: 8px 16px;
174 | font-size: 18px;
175 | color: var(--ink);
176 | background-color: var(--accent);
177 | border-radius: 16px;
178 | }
179 |
180 | mr-button.hover {
181 | background-color: var(--nav);
182 | }
183 |
184 | @media only screen and (max-width: 56rem) {
185 | .h1 {
186 | font-size: 36px;
187 | }
188 |
189 | .diptych, .triptych {
190 | grid-template-columns: 1fr;
191 | }
192 |
193 | .first {
194 | order: -1;
195 | }
196 | }
197 | /* THIS IS AUTO GENERATED FROM scripts/update-all-submodules.sh ANY DIRECT EDITS WILL NOT BE SAVED */
198 |
--------------------------------------------------------------------------------
/samples/opengraph.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Volumetrics-io/mrjs/8b31dbfc30f9c12ba74dd59ca44bde6e7de32f79/samples/opengraph.jpg
--------------------------------------------------------------------------------
/scripts/check-and-update-submodule.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ##################################################################
4 | ## !! IMPORTANT !! ##
5 | ## -- starts by stashing whatever local changes are made if any ##
6 | ## to not have issues with bumping the submodule commit. ##
7 | ## -- if submodule needs an update, then updates it and ##
8 | ## commits the changes in this main repo. ##
9 | ## -- applies the stashed changes on any type of exit (error, ##
10 | ## success, etc) to put the user back in their original ##
11 | ## state. ##
12 | ##################################################################
13 |
14 | # Function to handle script exit without needing an update
15 | cleanup_success() {
16 | apply_stash
17 | exit 0
18 | }
19 |
20 | # Function to handle script exit with an update
21 | cleanup_update() {
22 | apply_stash
23 | exit 2
24 | }
25 |
26 | # Function to handle unexpected errors
27 | error_handler() {
28 | echo "An error occurred. Cleaning up..."
29 | apply_stash
30 | exit 1
31 | }
32 |
33 | # Function to apply stashed changes
34 | apply_stash() {
35 | if [ "$STASH_APPLIED" = true ]; then
36 | echo "Applying stashed changes..."
37 | cd "$REPO_DIR" || exit 1
38 | git stash apply
39 | echo "Stashed changes have been reapplied."
40 | fi
41 | }
42 |
43 | # Check if a path argument was provided
44 | if [ "$#" -ne 1 ]; then
45 | echo "Usage: $0 path/to/your/submodule"
46 | exit 1
47 | fi
48 |
49 | echo "HI4"
50 | SUBMODULE_DIR="$1"
51 | echo "HI5"
52 | REPO_DIR=$(pwd)
53 | echo "HI6"
54 |
55 | trap error_handler ERR
56 | echo "HI6_0"
57 | trap cleanup_success EXIT
58 | echo "HI6_1"
59 |
60 | STASH_OUTPUT=$(git stash push -m "Auto-stashed by submodule update script")
61 | echo "HI6_2"
62 | if [[ "$STASH_OUTPUT" == *"No local changes to save"* ]]; then
63 | STASH_APPLIED=false
64 | else
65 | STASH_APPLIED=true
66 | fi
67 |
68 | echo "HI7"
69 |
70 | cd "$SUBMODULE_DIR" || exit 1
71 |
72 | git fetch origin
73 |
74 | LATEST_COMMIT=$(git rev-parse origin/main)
75 | CURRENT_COMMIT=$(git rev-parse HEAD)
76 |
77 | echo "HI8"
78 |
79 | if [ "$LATEST_COMMIT" == "$CURRENT_COMMIT" ]; then
80 | echo "Submodule $SUBMODULE_DIR is up to date."
81 | else
82 | echo "Submodule $SUBMODULE_DIR is not at the latest commit. Updating..."
83 | git checkout $LATEST_COMMIT
84 | cd "$REPO_DIR"
85 | git add "$SUBMODULE_DIR"
86 | git commit -m "Updated submodule $SUBMODULE_DIR to its latest repo commit: $LATEST_COMMIT"
87 | echo "Submodule updated and committed."
88 |
89 | # Change the trap for EXIT to use the cleanup_update function
90 | trap cleanup_update EXIT
91 | fi
92 |
--------------------------------------------------------------------------------
/scripts/check-if-submodule-needs-update.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Check if a path argument was provided
4 | if [ "$#" -ne 1 ]; then
5 | echo "Usage: $0 path/to/your/submodule"
6 | exit 1
7 | fi
8 |
9 | SUBMODULE_DIR="$1"
10 | REPO_DIR=$(pwd)
11 |
12 | # Function to apply stashed changes and exit
13 | cleanup() {
14 | cd "$REPO_DIR" || exit 1
15 | STASH_OUTPUT=$(git stash list)
16 | if [[ $STASH_OUTPUT == *"Auto-stashed by submodule update script"* ]]; then
17 | echo "Applying stashed changes..."
18 | git stash pop
19 | echo "Stashed changes have been reapplied."
20 | fi
21 | exit "$1"
22 | }
23 |
24 | # Handle unexpected errors
25 | trap 'cleanup 1' ERR
26 |
27 | # Stash local changes if any
28 | STASH_OUTPUT=$(git stash push -m "Auto-stashed by submodule update script")
29 | cd "$SUBMODULE_DIR" || exit 1
30 |
31 | git fetch origin
32 |
33 | LATEST_COMMIT=$(git rev-parse origin/main)
34 | CURRENT_COMMIT=$(git rev-parse HEAD)
35 |
36 | if [ "$LATEST_COMMIT" == "$CURRENT_COMMIT" ]; then
37 | echo "Submodule $SUBMODULE_DIR is up to date."
38 | cleanup 0
39 | else
40 | echo "Submodule $SUBMODULE_DIR is not at the latest commit. Please update."
41 | cleanup 2
42 | fi
43 |
--------------------------------------------------------------------------------
/scripts/create-docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Define base output directories
4 | output_dir='docs'
5 | js_api_dir="${output_dir}/js-api"
6 | js_api_utils_dir="${output_dir}/js-api-utils"
7 | js_api_extras_dir="${output_dir}/js-api-extras"
8 |
9 | # Remove the existing output directories if they exist and create new ones
10 | rm -rf "$js_api_dir" "$js_api_utils_dir" "$js_api_extras_dir"
11 | mkdir -p "$js_api_dir" "$js_api_utils_dir" "$js_api_extras_dir"
12 |
13 | # Function to process .js files
14 | process_js_files() {
15 | local source_dir=$1
16 | local output_dir=$2
17 | local ignore_index_js_subdirs=$3
18 | local counter=0 # Local counter for each call
19 |
20 | # Construct the base find command to exclude 'index.js' files directly
21 | local find_cmd="find \"$source_dir\" -type f -name '*.js' ! -name 'index.js' ! -name '_*'"
22 |
23 | # If ignoring subdirectories with index.js
24 | if [[ "$ignore_index_js_subdirs" == "true" ]]; then
25 | local ignore_dirs=$(find "$source_dir" -mindepth 2 -type f -name 'index.js' -exec dirname {} \; | sort | uniq)
26 | for dir in $ignore_dirs; do
27 | local escaped_dir=$(printf '%q' "$dir")
28 | find_cmd+=" ! -path \"$escaped_dir/*\""
29 | done
30 | fi
31 |
32 | eval "$find_cmd" | while read file; do
33 | # Format the counter with leading zeros
34 | printf -v formatted_counter "%03d" $counter
35 |
36 | # Generate new Markdown file name by prepending the counter to the base name of the .js file
37 | md_file="${output_dir}/${formatted_counter}$(basename "${file%.js}.md")"
38 |
39 | # Extract the title from the Markdown file name, accurately reflecting the original file name
40 | local original_file_name=$(basename "$file" .js)
41 | local title="${original_file_name}"
42 |
43 | # Generate Markdown content
44 | {
45 | echo '---'
46 | echo "title: ${title}"
47 | echo "github-path: https://github.com/volumetrics-io/mrjs/edit/main/${file}"
48 | echo '---'
49 | echo "# ${title}"
50 | echo ''
51 | jsdoc2md "$file"
52 | } > "$md_file"
53 |
54 | # Increment the local counter
55 | ((counter++))
56 | done
57 | }
58 |
59 | # Process files for each directory
60 | process_js_files "src" "$js_api_dir" true # true to ignore subdirs with index.js for js-api
61 | process_js_files "src/utils" "$js_api_utils_dir" false
62 | process_js_files "src/extras" "$js_api_extras_dir" false
63 |
--------------------------------------------------------------------------------
/src/core/MRElement.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class MRElement
3 | * @classdesc The first step in MRjs extending an HTMLElement. Used as a base for both `mr-app` and `mr-entity`.
4 | * @augments HTMLElement
5 | */
6 | export class MRElement extends HTMLElement {
7 | /**
8 | * @class
9 | * @description Constructs the basic information needed to separate an `MRElement` from an `HTMLElement`.
10 | */
11 | constructor() {
12 | super();
13 | this.environment = null;
14 | this.observer = null;
15 |
16 | // TODO: find alternative solution. This breaks with the switch to asychronous entity initialization @takahirox
17 | // Hack for the performance.
18 | // Element.getBoundingClientRect() is called from many places
19 | // mainly to sync the layout between DOM elements and 3D scene.
20 | // But .getBoundingClientRect() is slow because it may invoke reflow.
21 | // To avoid reflow in sync we use a hack with IntersectionObserver
22 | // that gives .boundingClientRect in callback function.
23 | //
24 | // There are some concerns in this approach.
25 | // 1. It might be costly to apply this technique to all MREntities.
26 | // Optimization might be necessary to apply it only to MREntities where
27 | // .getBoundingClientRect() is frequently executed.
28 | // 2. Since boundingClientRect is updated asynchronously, the latest values
29 | // may be set a few frames late. It is necessary to confirm whether this
30 | // is acceptable for the user experience and various systems. One possible
31 | // workaround, which is not perfect but simple, would be to add a new method
32 | // that is more efficient but may not return the latest value, in addition to
33 | // the regular .getBoundingClientRect() method. The caller can then choose
34 | // which method to use depending on the purpose.
35 | // 3. Asynchronous updates can make testing, problem investigation, and debugging
36 | // more difficult. For example, if a bug occurs only when updates are delayed by
37 | // greater than a certain number of frames, it may be difficult to reproduce the
38 | // issue and making problem investigation very challenging. (If IntersectionObserver
39 | // specification guarantees that the callback would always be called during the next
40 | // idle time after .observe() is executed, the problem would be less significant.)
41 | this._boundingClientRect = null;
42 | }
43 |
44 | /**
45 | * @function
46 | * @description Adding an entity as a sub-object of this entity.
47 | * @param {object} entity - the entity to be added.
48 | */
49 | add(entity) {}
50 |
51 | /**
52 | * @function
53 | * @description Removing an entity as a sub-object of this entity.
54 | * @param {object} entity - the entity to be removed.
55 | */
56 | removeEntity(entity) {}
57 |
58 | // TODO: find alternative solution. This breaks with the switch to asychronous entity initialization
59 | // /**
60 | // * @function
61 | // * @description Overrides getBoundingClientRect() to avoid reflow in sync as optimization
62 | // * @returns {object} rect - the bounding client rect of the HTMLElement representation of this MRElement.
63 | // */
64 | // getBoundingClientRect() {
65 | // // This is a fallback in case if .getBoundingClientRect() is called before
66 | // // ._boundingClientRect is initialized.
67 | // if (this._boundingClientRect === null) {
68 | // this._boundingClientRect = super.getBoundingClientRect();
69 | // }
70 | // // Assuming the values in the return value object are not overridden in the callers.
71 | // // If it happens, it affects to all the callers until ._boundingClientRect is refreshed.
72 | // return this._boundingClientRect;
73 | // }
74 | }
75 |
--------------------------------------------------------------------------------
/src/core/componentSystems/AudioSystem.js:
--------------------------------------------------------------------------------
1 | import { MRSystem } from 'mrjs/core/MRSystem';
2 |
3 | /**
4 | * @class AudioSystem
5 | * @classdesc This system manages spatial audio in the THREE.js scene.
6 | * @augments MRSystem
7 | */
8 | export class AudioSystem extends MRSystem {
9 | /**
10 | * @class
11 | * @description AudioSystem's Default constructor that sets up the audio listener and loader
12 | */
13 | constructor() {
14 | super();
15 |
16 | this.listener = new THREE.AudioListener();
17 | this.app.scene.add(this.listener);
18 |
19 | this.audioLoader = new THREE.AudioLoader();
20 | }
21 |
22 | /**
23 | * @function
24 | * @description The generic system update call. Updates the clipped view of every entity in this system's registry.
25 | * @param {number} deltaTime - given timestep to be used for any feature changes
26 | * @param {object} frame - given frame information to be used for any feature changes
27 | */
28 | update(deltaTime, frame) {
29 | this.listener.position.setFromMatrixPosition(this.app.user.origin.matrixWorld);
30 | this.listener.setRotationFromMatrix(this.app.user.origin.matrixWorld);
31 | }
32 |
33 | /**
34 | * @function
35 | * @description Called when the entity component is initialized
36 | * @param {object} entity - the entity being attached/initialized.
37 | */
38 | attachedComponent(entity) {
39 | entity.sound = new THREE.PositionalAudio(this.listener);
40 |
41 | entity.object3D.add(entity.sound);
42 |
43 | let comp = entity.components.get('audio');
44 |
45 | this.audioLoader.load(comp.src, (buffer) => {
46 | entity.sound.setBuffer(buffer);
47 | entity.sound.setRefDistance(comp.distance ?? 1);
48 |
49 | this.setAudioState(entity, comp);
50 | });
51 | }
52 |
53 | /**
54 | * @function
55 | * @description Called when the entity component is updated
56 | * @param {object} entity - the entity being updated based on the component.
57 | */
58 | updatedComponent(entity) {
59 | let comp = entity.components.get('audio');
60 | entity.sound.setRefDistance(comp.distance ?? 1);
61 | this.setAudioState(entity, comp);
62 | }
63 |
64 | /**
65 | * @function
66 | * @description Called when the entity component is detached
67 | * @param {object} entity - the entity being updated based on the component being detached.
68 | */
69 | detachedComponent(entity) {
70 | entity.sound.stop();
71 | entity.sound.dispose();
72 | entity.sound = null;
73 | }
74 |
75 | /**
76 | * @function
77 | * @description Updates the Audio State based on the user passed 'state' variable.
78 | * @param {object} entity - the entity being updated based on the component being detached.
79 | * @param {object} comp - component that contains the value of 'action'
80 | */
81 | setAudioState(entity, comp) {
82 | entity.sound.setLoop(comp.loop ?? false);
83 | switch (comp.action) {
84 | case 'play':
85 | if (entity.sound.isPlaying) {
86 | entity.sound.stop();
87 | }
88 | entity.sound.play();
89 | break;
90 | case 'pause':
91 | entity.sound.pause();
92 | case 'stop':
93 | default:
94 | entity.sound.stop();
95 | break;
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/core/componentSystems/BoundaryVisibilitySystem.js:
--------------------------------------------------------------------------------
1 | import { MRSystem } from 'mrjs/core/MRSystem';
2 | import { MRDivEntity } from 'mrjs/core/entities/MRDivEntity';
3 | import { MRPanelEntity } from 'mrjs/core/entities/MRPanelEntity';
4 |
5 | /**
6 | * @function
7 | * @description Observe a target MRDivEntity and make the associated object visible only if it is in visible position in a root MRDivEntity
8 | * @param {MRDivEntity} root - the root object being compared against
9 | * @param {MRDivEntity} target - the target object for which we're determining visiblity.
10 | * @returns {IntersectionObserver} - an observer for tracking visiblity
11 | */
12 | const observe = (root, target) => {
13 | // TODO: Callback is fired asynchronously so no guaranteed to be called immediately when the
14 | // visibility from the layout position changes. Therefore, the visibility of the associated
15 | // Object3D's might be updated a few frames later after when it really need to be. It might
16 | // affect the user experience. Fix it if possible.
17 | const observer = new IntersectionObserver(
18 | (entries) => {
19 | for (const entry of entries) {
20 | // TODO: Ensure to avoid the visibility set up collision. If multiple systems set up the visibility
21 | // independently, only the last system before render call can have an effect. This callback
22 | // is fired asynchronously so it may be said that this callback is executed before all the
23 | // sync systems.
24 | entry.target.object3D.visible = entry.intersectionRatio > 0;
25 | }
26 | // Somehow callback is sometimes not fired even though crossing the threshold so as fallback
27 | // always refresh the info as much as possible to keep it up-to-date.
28 | // It seems that the callback is always called once soon after observe() is called,
29 | // regardless of the intersection state of the entity.
30 | // TODO: Confirm whether this behavior is intended. If it is not, there may be future
31 | // behavior changes or it may not work as intended on certain platforms.
32 | // Alternative: Using multi-step threshold would mitigate the problem like [0.0, 0.05, 0.1]
33 | observer.disconnect();
34 | observer.observe(target);
35 | },
36 | {
37 | root: root,
38 | }
39 | );
40 | observer.observe(target);
41 | return observer;
42 | };
43 |
44 | /**
45 | * @class BoundaryVisibilitySystem
46 | * @classdesc Makes the entities invisible if they are outside of their parent panels
47 | * @augments MRSystem
48 | */
49 | export class BoundaryVisibilitySystem extends MRSystem {
50 | /**
51 | * @class
52 | * @description BoundaryVisibilitySystem's default constructor.
53 | */
54 | constructor() {
55 | super(false);
56 | this.observedEntities = new WeakSet();
57 | this.observers = new Map();
58 | }
59 |
60 | /**
61 | * @function
62 | * @description Called when a new entity is added to the scene.
63 | * @param {object} entity - the entity being added.
64 | */
65 | onNewEntity(entity) {
66 | // TODO: Support nested panels
67 | if (entity instanceof MRPanelEntity) {
68 | this.registry.add(entity);
69 | entity.traverse((child) => {
70 | if (child === entity) {
71 | return;
72 | }
73 |
74 | if (this.observedEntities.has(child)) {
75 | return;
76 | }
77 |
78 | this.observedEntities.add(child);
79 |
80 | this.observers.set(child, observe(entity, child));
81 | });
82 | } else if (!this.observedEntities.has(entity) && entity instanceof MRDivEntity) {
83 | // There is a chance that a child entity is added after parent panel addition.
84 | // Check registered panels and set up the observer if panels are found in parents.
85 | for (const panel of this.registry) {
86 | if (panel.contains(entity)) {
87 | this.observedEntities.add(entity);
88 | this.observers.set(child, observe(panel, entity));
89 | break;
90 | }
91 | }
92 | }
93 | }
94 |
95 | /**
96 | * @function
97 | * @description Called when an entity is removed from the scene.
98 | * @param {object} entity - the entity being added.
99 | */
100 | _entityRemoved(entity) {
101 | if (entity instanceof MRPanelEntity) {
102 | this.registry.delete(entity);
103 | entity.traverse((child) => {
104 | if (this.observedEntities.has(child)) {
105 | this.observedEntities.delete(child);
106 | this.observers.get(child).unobserve(child);
107 | this.observers.delete(child);
108 | }
109 | });
110 | } else if (this.observedEntities.has(entity)) {
111 | this.observedEntities.delete(entity);
112 | this.observers.get(entity).unobserve(entity);
113 | this.observers.delete(entity);
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/core/componentSystems/InstancingSystem.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | import { MRSystem } from 'mrjs/core/MRSystem';
4 | import { MREntity } from 'mrjs/core/MREntity';
5 |
6 | /**
7 | * @class InstancingSystem
8 | * @classdesc System that allows for instancing of meshes based on a given entity where the instances can be modified separately.
9 | * @augments MRSystem
10 | */
11 | export class InstancingSystem extends MRSystem {
12 | /**
13 | * @class
14 | * @description InstancingSystem's default constructor that sets up default instancing count, transformations, and mesh information.
15 | */
16 | constructor() {
17 | super();
18 |
19 | this.instanceCount = 5;
20 | this.transformations = [];
21 | this.instancedMesh = null;
22 | }
23 |
24 | /**
25 | * @function
26 | * @description The generic system update call. Updates the entity and its instances to their appropriate transformations and visuals
27 | * based on the picked predefined option.
28 | * @param {number} deltaTime - given timestep to be used for any feature changes
29 | * @param {object} frame - given frame information to be used for any feature changes
30 | */
31 | update(deltaTime, frame) {
32 | for (const entity of this.registry) {
33 | switch (entity.components.get('instancing')?.type) {
34 | case 'random':
35 | this.random(entity);
36 | break;
37 |
38 | default:
39 | break;
40 | }
41 | }
42 | }
43 |
44 | /**
45 | * @function
46 | * @description Determines what meshes are attached from this entity and When a component is attached.
47 | * Setups up instancing based on the predefined setup option and the entity's geometry (handling properly whether that be a group or mesh).
48 | * @param {MREntity} entity - the entity with the geometry to be instanced and the chosen setup option
49 | */
50 | attachedComponent(entity) {
51 | // ----- setup for instanced geometry -----
52 |
53 | let originalMesh = entity.object3D;
54 | let combinedGeometry = new THREE.BufferGeometry();
55 |
56 | // grab usable mesh
57 | if (originalMesh instanceof THREE.Mesh) {
58 | combinedGeometry = originalMesh.geometry.clone();
59 | } else if (originalMesh instanceof THREE.Group) {
60 | originalMesh.traverse((child) => {
61 | if (child instanceof THREE.Mesh) {
62 | const geometry = child.geometry.clone();
63 | geometry.applyMatrix4(child.matrixWorld); // Apply the child's world matrix
64 | combinedGeometry.merge(geometry);
65 | }
66 | });
67 | }
68 |
69 | // ----- create instances information -----
70 |
71 | // Setup for the to-be-used instance
72 | const instancedGeometry = new THREE.InstancedBufferGeometry();
73 | instancedGeometry.copy(combinedGeometry);
74 | for (let i = 0; i < this.instanceCount; ++i) {
75 | const matrix = new THREE.Matrix4();
76 | matrix.makeTranslation(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5);
77 |
78 | this.transformations.push(matrix);
79 | }
80 |
81 | // ----- add instances to scene -----
82 |
83 | // Create an InstancedMesh using the instanced geometry and matrices
84 | const material = new THREE.MeshBasicMaterial({ color: 0xffff00 });
85 | const instancedMesh = new THREE.InstancedMesh(instancedGeometry, material, this.instanceCount);
86 | instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
87 |
88 | // Set matrices for instances
89 | for (let i = 0; i < this.instanceCount; ++i) {
90 | instancedMesh.setMatrixAt(i, this.transformations[i]);
91 | }
92 | instancedMesh.instanceMatrix.needsUpdate = true;
93 | this.instancedMesh = instancedMesh;
94 |
95 | // Add the instanced mesh to the scene
96 | entity.object3D.add(instancedMesh);
97 | }
98 |
99 | /************ Some options for default instancing setup ************/
100 |
101 | /**
102 | * @function
103 | * @description An option for default instancing. Places the given entity instancing it at a bunch of random transformation locations.Uses threejs's `InstancedMesh`.
104 | * @param {MREntity} entity - the entity to be instanced in random locations
105 | */
106 | random = (entity) => {
107 | // update mesh for each instance
108 | for (let i = 0; i < this.instanceCount; ++i) {
109 | const matrix = new THREE.Matrix4();
110 | matrix.makeScale(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5);
111 | matrix.makeRotation(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5);
112 | matrix.makeTranslation(Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5);
113 |
114 | this.transformations[i].copy(matrix);
115 | this.instancedMesh.setMatrixAt(i, this.transformations[i]);
116 | }
117 | };
118 | }
119 |
--------------------------------------------------------------------------------
/src/core/componentSystems/LayoutSystem.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | import { MRSystem } from 'mrjs/core/MRSystem';
4 | import { MREntity } from 'mrjs/core/MREntity';
5 | import { MRDivEntity } from 'mrjs/core/entities/MRDivEntity';
6 | import { MRPanelEntity } from 'mrjs/core/entities/MRPanelEntity';
7 |
8 | /**
9 | * @class LayoutSystem
10 | * @classdesc System that allows for setup and handling of changing layout.
11 | * @augments MRSystem
12 | */
13 | export class LayoutSystem extends MRSystem {
14 | /**
15 | * @class
16 | * @description Constructor for the layout system. Uses the default System setup.
17 | */
18 | constructor() {
19 | super(false);
20 | this.tempPosition = new THREE.Vector3();
21 | }
22 |
23 | /**
24 | * @function
25 | * @description Called when a new entity is added to this system
26 | * @param {MREntity} entity - the entity being added.
27 | */
28 | onNewEntity(entity) {
29 | if (entity instanceof MRPanelEntity) {
30 | return;
31 | }
32 | if (entity instanceof MRDivEntity) {
33 | this.registry.add(entity);
34 | this.setLayoutPosition(entity);
35 | }
36 | }
37 |
38 | /**
39 | * @function
40 | * @description The generic system update call. Handles updating all 3D items to match whatever layout position is expected.
41 | * @param {number} deltaTime - given timestep to be used for any feature changes
42 | * @param {object} frame - given frame information to be used for any feature changes
43 | */
44 | update(deltaTime, frame) {
45 | for (const entity of this.registry) {
46 | this.setLayoutPosition(entity);
47 | }
48 | }
49 |
50 | /**
51 | * @function
52 | * @description Helper function for the update call. Sets the entity's appropriate 3D layout position based on window and entity expectations.
53 | * @param {MREntity} entity - the entity being updated.
54 | */
55 | setLayoutPosition(entity) {
56 | const rect = entity.getBoundingClientRect();
57 |
58 | const panel = entity.closest('mr-panel');
59 | if (!panel) {
60 | return;
61 | }
62 | const panelRect = panel.getBoundingClientRect();
63 |
64 | /** setup xy positioning of the entity */
65 |
66 | let innerWidth = parseFloat(panel.compStyle.width.split('px')[0]);
67 | let innerHeight = parseFloat(panel.compStyle.height.split('px')[0]);
68 | let centerX = innerWidth / 2;
69 | let centerY = innerHeight / 2;
70 |
71 | let windowWidth = panel.width;
72 | let windowHeight = panel.height;
73 | let centeredX = rect.left - panelRect.left - centerX;
74 | let centeredY = rect.top - panelRect.top - centerY;
75 |
76 | let threeX = (centeredX / innerWidth) * windowWidth;
77 | let threeY = (centeredY / innerHeight) * windowHeight;
78 |
79 | threeX += entity.width / 2;
80 | threeY += entity.height / 2;
81 |
82 | entity.object3D.position.setX(threeX);
83 | entity.object3D.position.setY(-threeY);
84 |
85 | /** setup z-index positioning of the entity */
86 |
87 | if (entity.compStyle.zIndex != 'auto') {
88 | // default zIndex values in css are in the 1000s - using this arbitrary divide to convert to an actual usable threejs value.
89 | entity.object3D.position.setZ(parseFloat(entity.compStyle.zIndex) / 1000);
90 |
91 | if (entity.compStyle.zIndex == entity.parentElement.compStyle.zIndex) {
92 | entity.object3D.position.z += 0.0001;
93 | }
94 | } else {
95 | entity.object3D.position.z = entity.parentElement.object3D.position.z + 0.001;
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/core/componentSystems/MaterialStyleSystem.js:
--------------------------------------------------------------------------------
1 | import { MRSystem } from 'mrjs/core/MRSystem';
2 | import { MRDivEntity } from 'mrjs/core/entities/MRDivEntity';
3 | import { MREntity } from 'mrjs/core/MREntity';
4 |
5 | /**
6 | * @class MaterialStyleSystem
7 | * @classdesc Handles style updates for all items.
8 | * @augments MRSystem
9 | */
10 | export class MaterialStyleSystem extends MRSystem {
11 | /**
12 | * @class
13 | * @description StyleSystem's default constructor with a starting framerate of 1/30.
14 | */
15 | constructor() {
16 | super(false);
17 |
18 | this.app.addEventListener('trigger-material-style-update', (e) => {
19 | // The event has the entity stored as its detail.
20 | if (e.detail !== undefined) {
21 | this._updateSpecificEntity(e.detail);
22 | }
23 | });
24 | }
25 |
26 | /**
27 | * @function
28 | * @param {object} entity - the MREntity being updated.
29 | * @description The per entity triggered update call. Handles updating all 3D items to match whatever geometry/style is expected whether that be a 2D setup or a 3D change.
30 | */
31 | _updateSpecificEntity(entity) {
32 | // Anything needed for mrjs defined entities - the order of the below matters
33 | if (entity instanceof MRDivEntity) {
34 | this.setBackground(entity);
35 | }
36 | this.setVisibility(entity);
37 |
38 | // User additional - Main Entity Style Change
39 | if (entity instanceof MREntity) {
40 | entity.updateMaterialStyle();
41 | }
42 | }
43 |
44 | /**
45 | * @function
46 | * @description The per-frame system update call. Handles updating all 3D items to match whatever geometry/style is expected whether that be a 2D setup or a 3D change.
47 | * @param {number} deltaTime - given timestep to be used for any feature changes
48 | * @param {object} frame - given frame information to be used for any feature changes
49 | */
50 | update(deltaTime, frame) {
51 | // For this system, since we have the 'per entity' and 'per scene event' update calls,
52 | // we dont need a main update call here.
53 | for (const entity of this.registry) {
54 | this._updateSpecificEntity(entity);
55 | }
56 | }
57 |
58 | /**
59 | * @function
60 | * @description Called when a new entity is added to the scene. Adds said new entity to the style's system registry.
61 | * @param {object} entity - the MREntity being touched by this function.
62 | */
63 | onNewEntity(entity) {
64 | this.registry.add(entity);
65 | }
66 |
67 | /**
68 | * @function
69 | * @param {object} entity - the MREntity being updated.
70 | * @description Sets the background based on compStyle and inputted css elements.
71 | */
72 | setBackground(entity) {
73 | mrjsUtils.color.setObject3DColor(entity.background, entity.compStyle.backgroundColor, entity.compStyle.opacity);
74 | }
75 |
76 | /**
77 | * @function
78 | * @description Sets the visibility of the MREntity based on its css 'visibility' property.
79 | * @param {object} entity - the MREntity being updated.
80 | */
81 | setVisibility(entity) {
82 | if (entity.compStyle.visibility && entity.compStyle.visibility !== 'none' && entity.compStyle.visibility !== 'collapse') {
83 | // visbility: hidden or visible are the options we care about
84 | const isVisible = entity.compStyle.visibility !== 'hidden';
85 | entity.object3D.visible = isVisible;
86 | if (entity.background) {
87 | // The background for MRDivEntity, but we want this css property allowed
88 | // for all, so using this checker to confirm the existence first.
89 | // entity.background.visible = bool;
90 | //
91 | // XXX - right now all backgrounds are set as visible=false by default in their
92 | // MRDivEntity constructors, so toggling them here isnt useful, but in future
93 | // if this is requested for use or we want to add a feature for more use of the
94 | // background - adding in toggling for this with the object will be useful.
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/core/componentSystems/PanelSystem.js:
--------------------------------------------------------------------------------
1 | import { MRSystem } from 'mrjs/core/MRSystem';
2 | import { MRPanelEntity } from 'mrjs/core/entities/MRPanelEntity';
3 |
4 | import { mrjsUtils } from 'mrjs';
5 |
6 | /**
7 | * @class PanelManagementSystem
8 | * @classdesc A system that manages the screen relative position of UI panels
9 | * @augments MRSystem
10 | */
11 | export class PanelSystem extends MRSystem {
12 | /**
13 | * @class
14 | * @description Constructor for the PanelManagementSystem system. Uses the default System setup.
15 | */
16 | constructor() {
17 | super(false);
18 | }
19 |
20 | eventUpdate = () => {
21 | for (const entity of this.registry) {
22 | entity.panel.scale.setScalar(mrjsUtils.app.scale);
23 | }
24 | };
25 |
26 | /**
27 | * @function
28 | * @description The generic system update call. keeps panel positions up to date.
29 | * @param {number} deltaTime - given timestep to be used for any feature changes
30 | * @param {object} frame - given frame information to be used for any feature changes
31 | */
32 | update(deltaTime, frame) {
33 | for (const entity of this.registry) {
34 | this.updatePanel(entity);
35 | }
36 | }
37 |
38 | /**
39 | * @function
40 | * @description Called when a new entity is added to this system
41 | * @param {object} entity - the entity being added.
42 | */
43 | onNewEntity(entity) {
44 | if (entity instanceof MRPanelEntity) {
45 | this.registry.add(entity);
46 | }
47 | }
48 |
49 | /**
50 | * @function
51 | * @description used to set the position of an individual panel
52 | * @param {object} entity - the entity being updated.
53 | */
54 | updatePanel(entity) {
55 | const rect = entity.getBoundingClientRect();
56 | const appRect = this.app.getBoundingClientRect();
57 |
58 | /** setup xy positioning of the entity */
59 |
60 | let innerWidth = global.appWidth;
61 | let innerHeight = global.appHeight;
62 | let centerX = innerWidth / 2;
63 | let centerY = innerHeight / 2;
64 |
65 | let windowWidth = global.viewPortWidth;
66 | let windowHeight = global.viewPortHeight;
67 |
68 | let top = rect.top - appRect.top;
69 | let left = rect.left - appRect.left;
70 | if (mrjsUtils.xr.isPresenting) {
71 | top = (top / window.screen.height) * mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;
72 | left = (left / window.innerWidth) * mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;
73 | }
74 | let centeredX = left - centerX;
75 | let centeredY = top - centerY;
76 |
77 | let threeX = (centeredX / innerWidth) * windowWidth;
78 | let threeY = (centeredY / innerHeight) * windowHeight;
79 |
80 | threeX += entity.width / 2;
81 | threeY += entity.height / 2;
82 |
83 | entity.panel.position.setX(threeX);
84 | entity.panel.position.setY(-threeY);
85 |
86 | /** setup z-index positioning of the entity */
87 |
88 | if (entity.compStyle.zIndex != 'auto') {
89 | // default zIndex values in css are in the 1000s - using this arbitrary divide to convert to an actual usable threejs value.
90 | entity.panel.position.setZ(parseFloat(entity.compStyle.zIndex) / 1000);
91 |
92 | if (entity.compStyle.zIndex == entity.parentElement.compStyle.zIndex) {
93 | entity.panel.position.z += 0.0001;
94 | }
95 | } else {
96 | entity.panel.position.z = 0;
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/core/componentSystems/SkyBoxSystem.js:
--------------------------------------------------------------------------------
1 | import { MRSystem } from 'mrjs/core/MRSystem';
2 | import { MRSkyBoxEntity } from 'mrjs/core/entities/MRSkyBoxEntity';
3 |
4 | /**
5 | * @class SkyBoxSystem
6 | * @classdesc Handles skybox interactions and updates for all items.
7 | * @augments MRSystem
8 | */
9 | export class SkyBoxSystem extends MRSystem {
10 | /**
11 | * @class
12 | * @description SkyBox's default constructor
13 | */
14 | constructor() {
15 | super(false);
16 |
17 | // used as a helper because we cant grab the most recently added
18 | // item from registry since it's a set.
19 | this._lastItem = null;
20 | }
21 |
22 | /**
23 | * @function
24 | * @description The generic system update call.
25 | * @param {number} deltaTime - given timestep to be used for any feature changes
26 | * @param {object} frame - given frame information to be used for any feature changes
27 | */
28 | update(deltaTime, frame) {
29 | // leave for when needed.
30 | }
31 |
32 | /**
33 | * @function
34 | * @description Called when a new entity is added to the scene. Adds said new entity to the style's system registry.
35 | * @param {object} entity - the entity being added.
36 | */
37 | onNewEntity(entity) {
38 | if (entity instanceof MRSkyBoxEntity) {
39 | if (entity.compStyle.scale == 'none') {
40 | // has no css scale attribute then use as default otherwise use as the user-defined version.
41 | const SCALING_OFFSET = 0.001;
42 | entity.object3D.scale.setScalar(this.registry.size == 0 ? 1 : this._lastItem.object3D.scale.z + SCALING_OFFSET);
43 | this._lastItem = entity;
44 | }
45 | this.registry.add(entity);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/core/componentSystems/StatsSystem.js:
--------------------------------------------------------------------------------
1 | import { MREntity } from 'mrjs/core/MREntity';
2 | import { MRSystem } from 'mrjs/core/MRSystem';
3 | import { MRStatsEntity } from 'mrjs/core/entities/MRStatsEntity';
4 |
5 | const REFRESH_SEC = 1.0;
6 |
7 | /**
8 | * @class StatsSystem
9 | * @classdesc Track the elapsed time across frames and update the fps counter periodically for `mr-stats`.
10 | * @augments MRSystem
11 | */
12 | export class StatsSystem extends MRSystem {
13 | /**
14 | * @class
15 | * @description StatsSystem's default constructor
16 | */
17 | constructor() {
18 | super(false);
19 | }
20 |
21 | /**
22 | * @function
23 | * @description Registers MRStatsEntity
24 | * @param {MREntity} entity - given entity that might be handled by this system
25 | */
26 | onNewEntity(entity) {
27 | if (entity instanceof MRStatsEntity) {
28 | this.registry.add(entity);
29 | }
30 | }
31 |
32 | /**
33 | * @function
34 | * @description Tracks the elapsed time and updates the fps counter periodically.
35 | * @param {number} deltaTime - the time elapsed since the last update call
36 | */
37 | update(deltaTime) {
38 | for (const stats of this.registry) {
39 | stats.frame++;
40 | stats.elapsedTime += deltaTime;
41 | if (stats.elapsedTime >= REFRESH_SEC) {
42 | // Note: We dont want to directly update the stats.textContent html element
43 | // as that will fill it in as an html value on the screen in 2D. We only
44 | // want to update the stats.textObj.text here directly for the 3D element
45 | // to update.
46 | stats.textObj.text = (stats.frame / stats.elapsedTime).toFixed(2) + ' fps';
47 |
48 | stats.frame = 0;
49 | stats.elapsedTime = 0.0;
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/core/entities/MRButtonEntity.js:
--------------------------------------------------------------------------------
1 | import { MRTextEntity } from 'mrjs/core/entities/MRTextEntity';
2 |
3 | /**
4 | * @class MRButtonEntity
5 | * @classdesc 3D representation of a Button mimicking the html version. `mr-button`
6 | * @augments MRTextEntity
7 | */
8 | export class MRButtonEntity extends MRTextEntity {
9 | /**
10 | * @class
11 | * @description Constructor for the Button entity, does the default.
12 | */
13 | constructor() {
14 | super();
15 | this.background.castShadow = true;
16 | this.object3D.name = 'button';
17 | this.ignoreStencil = true;
18 | }
19 | }
20 |
21 | customElements.get('mr-button') || customElements.define('mr-button', MRButtonEntity);
22 |
--------------------------------------------------------------------------------
/src/core/entities/MRDivEntity.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | import { MREntity } from 'mrjs/core/MREntity';
4 |
5 | import { mrjsUtils } from 'mrjs';
6 |
7 | /**
8 | * @class MRDivEntity
9 | * @classdesc The MREntity that is used to solely describe UI Elements. Defaults as the html `mr-div` representation. `mr-div`
10 | * @augments MREntity
11 | */
12 | export class MRDivEntity extends MREntity {
13 | /**
14 | * @class
15 | * @description Constructor sets up the defaults for the background mesh, scaling, and world relevant elements.
16 | */
17 | constructor() {
18 | super();
19 | this.worldScale = new THREE.Vector3();
20 | this.halfExtents = new THREE.Vector3();
21 | this.physics.type = 'ui';
22 |
23 | const geometry = mrjsUtils.geometry.UIPlane(1, 1, [0], 18);
24 | const material = new THREE.MeshStandardMaterial({
25 | color: 0xfff,
26 | roughness: 0.7,
27 | metalness: 0.0,
28 | side: THREE.DoubleSide,
29 | });
30 |
31 | this.background = new THREE.Mesh(geometry, material);
32 | this.background.receiveShadow = true;
33 | this.background.renderOrder = 3;
34 | this.background.visible = false;
35 | this.background.name = 'background';
36 | this.object3D.add(this.background);
37 | this.object3D.name = 'mrDivEntity';
38 |
39 | // allow stenciling when needed by default for UI elements, but also allows
40 | // overriding when needed.
41 | this.ignoreStencil = false;
42 | }
43 |
44 | _storedWidth = -1;
45 | _storedHeight = -1;
46 | _storedBorderRadii = -1;
47 |
48 | /**
49 | * @function
50 | * @description Calculates the height of the Entity based on the viewing-client's shape. If in Mixed Reality, adjusts the value appropriately.
51 | * @returns {number} - the resolved height
52 | */
53 | get height() {
54 | const rect = this.getBoundingClientRect();
55 |
56 | if (mrjsUtils.xr.isPresenting) {
57 | return rect.height / mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;
58 | }
59 | return (rect.height / global.appHeight) * global.viewPortHeight;
60 | }
61 |
62 | /**
63 | * @function
64 | * @description Calculates the width of the Entity based on the viewing-client's shape. If in Mixed Reality, adjusts the value appropriately.
65 | * @returns {number} - the resolved width
66 | */
67 | get width() {
68 | const rect = this.getBoundingClientRect();
69 |
70 | if (mrjsUtils.xr.isPresenting) {
71 | return rect.width / mrjsUtils.display.VIRTUAL_DISPLAY_RESOLUTION;
72 | }
73 | return (rect.width / global.appWidth) * global.viewPortWidth;
74 | }
75 |
76 | /**
77 | * @function
78 | * @description Calculates the border radius of the img based on the img tag in the shadow root
79 | * @returns {number} - the resolved height
80 | */
81 | get borderRadii() {
82 | return this.compStyle.borderRadius.split(' ').map((r) => mrjsUtils.css.domToThree(r));
83 | }
84 |
85 | /**
86 | * @function
87 | * @description Adding an entity as a sub-object of this panel (for example an mr-model, button, etc).
88 | * @param {MREntity} entity - the entity to be added.
89 | */
90 | add(entity) {
91 | // `this` must have `mr-panel` as its closest parent entity for threejs to handle positioning appropriately.
92 | let panel = this.closest('mr-panel');
93 | if (panel && entity instanceof MRDivEntity) {
94 | panel.add(entity);
95 | } else {
96 | this.object3D.add(entity.object3D);
97 | }
98 |
99 | // slight bump needed to avoid overlapping, glitchy visuals.
100 | entity.object3D.position.z = this.object3D.position.z + 0.001;
101 | }
102 |
103 | /**
104 | * @function
105 | * @description Removing an entity as a sub-object of this panel (for example an mr-model, button, etc).
106 | * @param {MREntity} entity - the entity to be removed added.
107 | */
108 | removeEntity(entity) {
109 | // `this` must have `mr-panel` as its closest parent entity for threejs to handle positioning appropriately.
110 | let panel = this.closest('mr-panel');
111 | if (panel && entity instanceof MRDivEntity) {
112 | panel.removeEntity(entity);
113 | } else {
114 | this.object3D.remove(entity.object3D);
115 | }
116 | }
117 |
118 | /**
119 | * @function
120 | * @description (async) connects the background geometry of this item to an actual UIPlane geometry.
121 | */
122 | async connected() {
123 | this.background.geometry = mrjsUtils.geometry.UIPlane(this.width, this.height, [0], 18);
124 | }
125 | }
126 |
127 | customElements.get('mr-div') || customElements.define('mr-div', MRDivEntity);
128 |
--------------------------------------------------------------------------------
/src/core/entities/MRHyperlinkEntity.js:
--------------------------------------------------------------------------------
1 | import { MRTextEntity } from 'mrjs/core/entities/MRTextEntity';
2 |
3 | /**
4 | * @class MRHyperlinkEntity
5 | * @classdesc 3D representation of a hyperlink. `mr-a`
6 | * @augments MRTextEntity
7 | */
8 | export default class MRHyperlinkEntity extends MRTextEntity {
9 | /**
10 | * Constructor for the Model entity, does the default.
11 | */
12 | constructor() {
13 | super();
14 | this.background.castShadow = true;
15 | this.object3D.name = 'hyperlink';
16 | }
17 |
18 | /**
19 | * @function
20 | * @description Creates the link object if it's not already created and handles the href and
21 | * target attributes.
22 | */
23 | _createLink() {
24 | if (!this.link) {
25 | this.link = document.createElement('a');
26 | this.link.setAttribute('href', this.getAttribute('href') ?? undefined);
27 | this.link.setAttribute('target', this.getAttribute('target') ?? undefined);
28 | }
29 | }
30 |
31 | /**
32 | * @function
33 | * @description Grabs the href of the link object
34 | * @returns {string} the href value
35 | */
36 | get href() {
37 | this._createLink();
38 | return this.link.getAttribute('href');
39 | }
40 |
41 | /**
42 | * @function
43 | * @description Sets the href of the link object
44 | * @param {string} src_str - the new href value
45 | */
46 | set href(src_str) {
47 | this._createLink();
48 | this.link.setAttribute('href', src_str);
49 | }
50 |
51 | /**
52 | * @function
53 | * @description (async) makes sure the link object is created and sets up event
54 | * listeners for touchstart and click.
55 | */
56 | async connected() {
57 | super.connected();
58 | this._createLink();
59 |
60 | this.addEventListener('touchstart', () => {
61 | this.classList.add('active');
62 | });
63 |
64 | this.addEventListener('click', () => {
65 | this.link.click();
66 | });
67 | }
68 | }
69 |
70 | customElements.get('mr-a') || customElements.define('mr-a', MRHyperlinkEntity);
71 |
--------------------------------------------------------------------------------
/src/core/entities/MRImageEntity.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | import { MRMediaEntity } from 'mrjs/core/entities/MRMediaEntity';
4 |
5 | import { mrjsUtils } from 'mrjs';
6 |
7 | /**
8 | * @class MRImageEntity
9 | * @classdesc Base html image represented in 3D space. `mr-image`
10 | * @augments MRMediaEntity
11 | */
12 | export class MRImageEntity extends MRMediaEntity {
13 | /**
14 | * @class
15 | * @description Constructs a base image entity using a UIPlane and other 3D elements as necessary.
16 | */
17 | constructor() {
18 | super();
19 |
20 | // object3D and rest of mrvideo is pre-created in MRMediaEntity
21 | this.object3D.name = 'image';
22 | }
23 |
24 | /**
25 | * @function
26 | * @description Gets the width of the internal media object
27 | * @returns {number} width - the value of the width
28 | */
29 | get mediaWidth() {
30 | return this.media.width;
31 | }
32 |
33 | /**
34 | * @function
35 | * @description Gets the height of the internal media object
36 | * @returns {number} height - the value of the height
37 | */
38 | get mediaHeight() {
39 | return this.media.height;
40 | }
41 |
42 | /**
43 | * @function
44 | * @description (async) handles setting up this Image and associated 3D geometry style (from css) once it is connected to run as an entity component.
45 | */
46 | async connected() {
47 | this.media = document.createElement('img');
48 | await super.connected();
49 | }
50 |
51 | /**
52 | * @function
53 | * @description Loads the Media texture of the setup this.media object based on its html source info.
54 | */
55 | loadMediaTexture() {
56 | mrjsUtils.material
57 | .loadTextureAsync(this.media.src)
58 | .then((texture) => {
59 | this.texture = texture;
60 | this.object3D.material.map = texture;
61 | })
62 | .catch((error) => {
63 | console.error('Error loading texture:', error);
64 | });
65 | }
66 |
67 | /**
68 | * @function
69 | * @description Callback function of MREntity - Updates the image's cover,fill,etc based on the mutation request.
70 | * @param {object} mutation - the update/change/mutation to be handled.
71 | */
72 | mutated(mutation) {
73 | // Mutations are only understood by their actual type. Any mutation
74 | // passed through MRMediaEntity directly is undefined since it is not
75 | // a direct element for users. So we do the if-check here and then
76 | // follow the same as the parent's functionality.
77 | if (mutation.type == 'attributes' && mutation.attributeName == 'src') {
78 | super.mutated();
79 | }
80 | }
81 | }
82 |
83 | customElements.get('mr-img') || customElements.define('mr-img', MRImageEntity);
84 |
--------------------------------------------------------------------------------
/src/core/entities/MRLightEntity.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | import { MREntity } from 'mrjs/core/MREntity';
4 |
5 | /**
6 | * @class MRLightEntity
7 | * @classdesc Represents lights in 3D space. `mr-light`
8 | * @augments MREntity
9 | */
10 | export class MRLightEntity extends MREntity {
11 | /**
12 | * @class
13 | * @description Constructs the base 3D object.
14 | */
15 | constructor() {
16 | super();
17 | this.object3D = new THREE.PointLight({});
18 | this.object3D.name = 'pointLight';
19 | }
20 |
21 | /**
22 | * @function
23 | * @description (async) handles setting up this Light once it is connected to run as an entity component.
24 | */
25 | async connected() {
26 | this.object3D.color.setStyle(this.getAttribute('color'));
27 | this.object3D.intensity = parseFloat(this.getAttribute('intensity')) ?? 1;
28 | }
29 |
30 | /**
31 | * @function
32 | * @description (async) Updates the lights color and intensity as requested.
33 | * @param {object} mutation - the update/change/mutation to be handled.
34 | */
35 | mutated = (mutation) => {
36 | if (mutation.type != 'attributes') {
37 | return;
38 | }
39 | switch (mutation.attributeName) {
40 | case 'color':
41 | // TODO - set via css
42 | this.object3D.color.setStyle(this.getAttribute('color'));
43 | break;
44 |
45 | case 'intensity':
46 | this.object3D.intensity = this.getAttribute('intensity');
47 | break;
48 |
49 | default:
50 | break;
51 | }
52 | };
53 | }
54 |
55 | customElements.get('mr-light') || customElements.define('mr-light', MRLightEntity);
56 |
--------------------------------------------------------------------------------
/src/core/entities/MRSkyBoxEntity.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | import { MREntity } from 'mrjs/core/MREntity';
4 | import { MRSystem } from 'mrjs/core/MRSystem';
5 |
6 | /**
7 | * @class MRSkyBoxEntity
8 | * @classdesc The skybox entity that allows users to give multiple images to pattern into the 3D background space. `mr-skybox`
9 | * @augments MREntity
10 | */
11 | export class MRSkyBoxEntity extends MREntity {
12 | /**
13 | * @class
14 | * @description Constructor for skybox - defaults to the usual impl of an Entity.
15 | */
16 | constructor() {
17 | super();
18 | this.object3D.name = 'skybox';
19 |
20 | this.skybox = null;
21 | this.textureLoadedCallbacks = [];
22 | }
23 |
24 | /**
25 | * @function
26 | * @description Callback function triggered when the texture is successfully loaded.
27 | * It sets the loaded texture as the background and notifies all registered callbacks.
28 | * @param {THREE.Texture} texture - The loaded texture.
29 | */
30 | onTextureLoaded(texture) {
31 | if (this.skybox) {
32 | if (Array.isArray(texture.images) && texture.images.length === 6) {
33 | // Handle cube texture case
34 | if (this.skybox.material !== undefined) {
35 | this.skybox.material.dispose();
36 | }
37 | this.skybox.material = new THREE.MeshStandardMaterial({
38 | envMap: texture,
39 | side: THREE.BackSide, // Render only on the inside
40 | });
41 | } else {
42 | // Handle single texture case
43 | if (this.skybox.material !== undefined) {
44 | this.skybox.material.dispose();
45 | }
46 | this.skybox.material = new THREE.MeshBasicMaterial({
47 | map: texture,
48 | side: THREE.BackSide, // Render only on the inside
49 | opacity: 1,
50 | });
51 | }
52 | }
53 | this.textureLoadedCallbacks.forEach((callback) => callback(texture));
54 | }
55 |
56 | /**
57 | * @function
58 | * @description (async) Lifecycle method that is called when the entity is connected.
59 | * This method initializes and starts the texture loading process.
60 | */
61 | async connected() {
62 | // you can have texturesList be all individual textures
63 | // or you can store them in a specified path and just
64 | // load them up solely by filename in that path.
65 |
66 | this.texturesList = mrjsUtils.html.resolvePath(this.getAttribute('src'));
67 | if (!this.texturesList) {
68 | return;
69 | }
70 |
71 | const textureNames = this.texturesList.split(',');
72 | const path = this.getAttribute('pathToTextures');
73 | const textureUrls = textureNames.map((name) => mrjsUtils.html.resolvePath(path ? path + name : name));
74 |
75 | let geometry;
76 | let textureLoader;
77 | if (textureNames.length > 1) {
78 | geometry = new THREE.BoxGeometry(900, 900, 900);
79 | textureLoader = new THREE.CubeTextureLoader();
80 | textureLoader.load(textureUrls, this.onTextureLoaded.bind(this));
81 | } else if (textureUrls.length == 1) {
82 | geometry = new THREE.SphereGeometry(900, 32, 16);
83 | textureLoader = new THREE.TextureLoader();
84 | textureLoader.load(textureUrls[0], this.onTextureLoaded.bind(this));
85 | }
86 |
87 | if (this.skybox) {
88 | // Remove existing skybox if present
89 | this.object3D.remove(this.skybox);
90 | this.skybox.dispose();
91 | }
92 | this.skybox = new THREE.Mesh(geometry); // going to passively load texture on async
93 | this.object3D.add(this.skybox);
94 | }
95 |
96 | /**
97 | * @function
98 | * @description Set the opacity of the skybox itself. Useful for blending between the outside and MR. Also
99 | * useful for cases where you want to blend between different skybox versions.
100 | */
101 | set setOpacity(val) {
102 | this.object3D.traverse((child) => {
103 | if (child.isMesh) {
104 | child.material.transparent = true;
105 | child.material.opacity = val;
106 | child.material.needsUpdate = true;
107 | }
108 | });
109 | }
110 |
111 | /**
112 | * @function
113 | * @description On load event function - right now defaults to do nothing.
114 | */
115 | onLoad = () => {};
116 | }
117 | customElements.define('mr-skybox', MRSkyBoxEntity);
118 |
--------------------------------------------------------------------------------
/src/core/entities/MRStatsEntity.js:
--------------------------------------------------------------------------------
1 | import { MRTextEntity } from 'mrjs/core/entities/MRTextEntity';
2 |
3 | /**
4 | * @class MRStatsEntity
5 | * @classdesc The FPS counter entity. For simplicity, easy implementation, and good performance,
6 | * it is based on MRTextEntity and just shows the FPS counter number as text for now.
7 | * Ideally we want to improve later, like improving the visual quality and more info.
8 | * Note that stats entity that has a huge bad performance impact doesn't really make
9 | * sense so it should be kept simple and fast.
10 | * @augments MRTextEntity
11 | */
12 | export class MRStatsEntity extends MRTextEntity {
13 | /**
14 | * @class
15 | * @description Constructor for the MRStatsEntity object.
16 | * Initializes some variables used to track and calculate the fps.
17 | */
18 | constructor() {
19 | super();
20 | this.frame = 0;
21 | this.elapsedTime = 0.0;
22 | this.object3D.name = 'stats';
23 | }
24 | }
25 |
26 | customElements.get('mr-stats') || customElements.define('mr-stats', MRStatsEntity);
27 |
--------------------------------------------------------------------------------
/src/core/entities/MRVideoEntity.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | import { MRMediaEntity } from 'mrjs/core/entities/MRMediaEntity';
4 |
5 | import { mrjsUtils } from 'mrjs';
6 |
7 | /**
8 | * @class MRVideoEntity
9 | * @classdesc Base html video represented in 3D space. `mr-video`
10 | * @augments MRMediaEntity
11 | */
12 | export class MRVideoEntity extends MRMediaEntity {
13 | /**
14 | * @class
15 | * @description Constructs a base video entity using a UIPlane and other 3D elements as necessary.
16 | */
17 | constructor() {
18 | super();
19 | this.object3D.name = 'video';
20 | this.playing = false;
21 | }
22 |
23 | /**
24 | * @function
25 | * @description Calculates the width of the video based on the video tag itself
26 | * @returns {number} - the resolved width
27 | */
28 | get mediaWidth() {
29 | return this.media.videoWidth;
30 | }
31 |
32 | /**
33 | * @function
34 | * @description Calculates the height of the video based on the video tag itself
35 | * @returns {number} - the resolved height
36 | */
37 | get mediaHeight() {
38 | return this.media.videoHeight;
39 | }
40 |
41 | /**
42 | * @function
43 | * @description Loads the associated video into 3D based on its html properties.
44 | */
45 | loadMediaTexture() {
46 | mrjsUtils.material
47 | .loadVideoTextureAsync(this.media)
48 | .then((texture) => {
49 | this.texture = texture;
50 | this.object3D.material.map = texture;
51 | this.playing = true; // since we have videos auto play on silent to start
52 | })
53 | .catch((error) => {
54 | console.error('Error loading texture:', error);
55 | });
56 | }
57 |
58 | /**
59 | * @function
60 | * @description (async) handles setting up this video and associated 3D geometry style (from css) once it is connected to run as an entity component.
61 | */
62 | async connected() {
63 | this.media = document.createElement('video');
64 | this.media.setAttribute('crossorigin', 'anonymous');
65 |
66 | await super.connected();
67 | }
68 |
69 | /**
70 | * @function
71 | * @description Sets the srcObject of the video media (since it uses 'srcObject' instead of 'src' like other items).
72 | * @param {string} src - the string to the new source object we want
73 | */
74 | set srcObject(src) {
75 | this.media.srcObject = src;
76 | // on loadeddata event, update the objectFitDimensions
77 | this.media.addEventListener('loadeddata', () => {
78 | this.computeObjectFitDimensions();
79 | });
80 | }
81 |
82 | /**
83 | * @function
84 | * @description Plays the video in the shadow root
85 | */
86 | play() {
87 | this.media.play();
88 | this.playing = true;
89 | }
90 |
91 | /**
92 | * @function
93 | * @description Pauses the video in the shadow root
94 | */
95 | pause() {
96 | this.media.pause();
97 | this.playing = false;
98 | }
99 | }
100 |
101 | customElements.get('mr-video') || customElements.define('mr-video', MRVideoEntity);
102 |
--------------------------------------------------------------------------------
/src/core/entities/MRVolumeEntity.js:
--------------------------------------------------------------------------------
1 | import { MREntity } from 'mrjs/core/MREntity';
2 |
3 | /**
4 | * @class MRVolumeEntity
5 | * @classdesc Representation of a visible region in 3D space. Models and other entities can move
6 | * throughout the space and leave the space, yet will only be rendered in the visual area of
7 | * the volume. From a conceptual perspective it is considered a ‘clipping volume’.
8 | * @augments MREntity
9 | */
10 | export class MRVolumeEntity extends MREntity {
11 | /**
12 | * @class
13 | * @description Creates the volume as a base THREE.js object3D
14 | */
15 | constructor() {
16 | super();
17 | this.volume = new THREE.Object3D();
18 | this.object3D.add(this.volume);
19 | }
20 |
21 | /**
22 | * @function
23 | * @description (async) handles creating clipping geometry around the entire volume for visible restrictions.
24 | */
25 | async connected() {
26 | this.clipping = new MRClippingGeometry(new THREE.BoxGeometry(1, 1, 1));
27 | this.ignoreStencil = true;
28 |
29 | this.addEventListener('anchored', () => {
30 | if (this.plane) {
31 | let height = this.plane.dimensions.x <= this.plane.dimensions.z ? this.plane.dimensions.x : this.plane.dimensions.z;
32 | this.volume.position.y += height / 2;
33 | this.clipping.geometry.copy(new THREE.BoxGeometry(this.plane.dimensions.x, height, this.plane.dimensions.z));
34 | }
35 | });
36 | }
37 | }
38 |
39 | customElements.get('mr-volume') || customElements.define('mr-volume', MRVolumeEntity);
40 |
--------------------------------------------------------------------------------
/src/core/user/MRUser.js:
--------------------------------------------------------------------------------
1 | import { MRHand } from 'mrjs/core/user/MRHand';
2 |
3 | /**
4 | * @class MRUser
5 | */
6 | export default class MRUser {
7 | forward = new THREE.Object3D();
8 |
9 | origin = new THREE.Object3D();
10 |
11 | spotlight = null;
12 |
13 | hands = {
14 | left: null,
15 | right: null,
16 | };
17 | /**
18 | * Constructor for the MRUser class, sets up the camera, hands, and spotlight information.
19 | * @param {object} camera - the threejs camera to be used as the user's pov.
20 | * @param {object} scene - the threejs scene in which the user will be immersed.
21 | */
22 | constructor(camera, scene) {
23 | this.camera = camera;
24 |
25 | this.hands.left = new MRHand('left', scene);
26 | this.hands.right = new MRHand('right', scene);
27 |
28 | this.camera.add(this.forward);
29 | this.forward.position.setZ(-0.5);
30 | this.forward.position.setX(0.015);
31 |
32 | this.camera.add(this.origin);
33 | this.origin.position.setX(0.015);
34 |
35 | this.leftWorldPosition = new THREE.Vector3();
36 | this.rightWorldPosition = new THREE.Vector3();
37 | this.worldPosition = new THREE.Vector3();
38 |
39 | this.leftDistance = 0;
40 | this.rightDistance = 0;
41 |
42 | this.spotLightScale = 1;
43 | }
44 |
45 | /**
46 | * Initializes the spotlight associated with the user's pov.
47 | * @returns {object} spotlight - the spotlight to be used.
48 | */
49 | initSpotlight() {
50 | this.spotlight = new THREE.Mesh(new THREE.CircleGeometry(1.3, 64), new THREE.MeshBasicMaterial());
51 | this.spotlight.material.colorWrite = false;
52 | this.spotlight.renderOrder = 2;
53 | this.spotlight.rotation.x = -Math.PI / 2;
54 |
55 | return this.spotlight;
56 | }
57 |
58 | /**
59 | * The update function for a user, its spotlight, and its hands.
60 | */
61 | update() {
62 | this.hands.left.update();
63 | this.hands.right.update();
64 |
65 | if (this.spotlight) {
66 | this.worldPosition.setFromMatrixPosition(this.origin.matrixWorld);
67 | this.worldPosition.y = 0;
68 |
69 | if (this.hands.left.active) {
70 | this.hands.left.controller.getWorldPosition(this.leftWorldPosition);
71 | this.leftWorldPosition.y = 0;
72 | this.leftDistance = this.worldPosition.distanceTo(this.leftWorldPosition);
73 | } else {
74 | this.leftDistance = 0;
75 | }
76 |
77 | if (this.hands.right.active) {
78 | this.hands.right.controller.getWorldPosition(this.rightWorldPosition);
79 | this.rightWorldPosition.y = 0;
80 | this.rightDistance = this.worldPosition.distanceTo(this.rightWorldPosition);
81 | } else {
82 | this.rightDistance = 0;
83 | }
84 |
85 | this.spotLightScale = this.leftDistance > this.rightDistance ? this.leftDistance : this.rightDistance;
86 |
87 | if (this.spotLightScale > 0) {
88 | this.spotLightScale += 1;
89 | this.spotlight.scale.setScalar(this.spotLightScale);
90 | }
91 |
92 | this.spotlight.position.setFromMatrixPosition(this.origin.matrixWorld);
93 | this.spotlight.position.y = 0;
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/dataTypes/MRClippingGeometry.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class MRClippingGeometry
3 | * @classdesc Geometry used in the clipping plane step. Separated out for clarity in the calculations.
4 | */
5 | export class MRClippingGeometry {
6 | planes = [];
7 |
8 | intersection = false;
9 |
10 | global = false;
11 |
12 | // NOTE: There is probably a better way to do this
13 |
14 | // TODO - separate this out so it's only needed within the Clipping Geometry System. This is not useful being separate from that
15 | // must do that before deletion
16 |
17 | /**
18 | * @class
19 | * @description Constructor for the clipping geometry class. Sets the internal geometry object to the geometry that is passed through.
20 | * @param {object} geometry - The geometry to be captured internally by `this.geometry`.
21 | */
22 | constructor(geometry) {
23 | // Limits to one segment BoxGeometry instance like created with
24 | // "new BoxGeometry(width, height, depth);" for simplicity for now.
25 |
26 | // The geometry type limitation may not be immediately obvious to users of this module.
27 | // If unsupported geometry is passed, no errors may be raised, but the behavior may
28 | // become erratic, and such bugs can be difficult to investigate. This check is in
29 | // place to avoid such unnecessary effort.
30 | if (geometry.type !== 'BoxGeometry' || geometry.parameters?.widthSegments !== 1 || geometry.parameters?.heightSegments !== 1 || geometry.parameters?.depthSegments !== 1) {
31 | throw new Error('Unsupported Clipping geometry type.');
32 | }
33 |
34 | this.geometry = geometry;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/dataTypes/MRPlane.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class MRPlane
3 | * @classdesc a name space representation of an MR Plane
4 | */
5 | export class MRPlane {
6 | // The semantic label of the plane
7 | // possible values: wall, table, ceiling, floor
8 | label = null;
9 |
10 | // The orientation of the plane
11 | // possible values: horizontal, vertical
12 | orientation = null;
13 |
14 | // the physical dimensions of the plane
15 | dimensions = new THREE.Vector3();
16 |
17 | // a flag indicating whether or not the plane is currently occupied by an anchored entity
18 | occupied = false; // Each plane can have one Entity anchored to it for simplicity.
19 |
20 | // the THREE.js Mesh representation
21 | mesh = null;
22 |
23 | // the RAPIER physics body
24 | body = null;
25 | }
26 |
--------------------------------------------------------------------------------
/src/defaultStyle.css:
--------------------------------------------------------------------------------
1 | mr-app {
2 | display: block;
3 | height: 100vh;
4 | width: 100%;
5 | }
6 |
7 | .inXR {
8 | width: 100vw;
9 | }
10 |
11 | mr-app * {
12 | box-sizing: border-box;
13 | opacity: 0%;
14 | }
15 |
16 | mr-app > canvas {
17 | position:fixed;
18 | visibility:visible;
19 | opacity: 100%;
20 | z-index: 999;
21 | }
22 |
23 | mr-div, mr-button, mr-img, mr-video, mr-a, mr-text, mr-textarea, mr-textfield, mr-stats {
24 | display: inline-block;
25 | position: relative;
26 | z-index: inherit;
27 | opacity: 100%;
28 | }
29 |
30 | mr-text, mr-stats {
31 | line-height: 100%;
32 | font-size: 16px;
33 | }
34 |
35 | mr-textarea, mr-textfield {
36 | background-color: rgba(255, 255, 255, 0.75);
37 | font-size: 16px;
38 | border-radius: 1%;
39 | }
40 |
41 | mr-textfield {
42 | min-height: 2em;
43 | font-size: 100%;
44 | }
45 |
46 | mr-textarea {
47 | min-height: 7em;
48 | }
49 |
50 | mr-panel {
51 | background-color: #fff;
52 | border-radius: 2%;
53 | position: fixed;
54 | overflow: auto;
55 | height: 100vh;
56 | width: 100vw;
57 | }
58 |
59 | mr-img, mr-video {
60 | object-fit: contain;
61 | }
62 |
63 | mr-button {
64 | padding: 5px;
65 | text-align: center;
66 | vertical-align: middle;
67 | background-color: #8a8a8a;
68 | border-radius: 0.5%;
69 | /* animation: back 0.25s ease-out forwards; */
70 | width: fit-content;
71 | }
72 |
73 | mr-button.hover {
74 | background-color: #333;
75 | z-index: 5; /* end */
76 | /* animation: forward 0.25s ease-in forwards; */
77 | }
78 |
79 | mr-a {
80 | color: darkblue;
81 | }
82 |
83 | mr-a.hover {
84 | color: blue;
85 | z-index: 5;
86 | }
87 |
88 | mr-a.active {
89 | color: purple;
90 |
91 | }
92 |
93 | /* these work differently from in XR than expected, laurent should look into it */
94 | /* button animation! */
95 |
96 | @keyframes forward {
97 | to {
98 | z-index: 5; /* end */
99 | }
100 | }
101 |
102 | @keyframes back {
103 | to {
104 | z-index: 1; /* end */
105 | }
106 | }
107 |
108 |
109 | body {
110 | margin: 0; /* unclear why we need this */
111 | }
112 |
--------------------------------------------------------------------------------
/src/extras/index.js:
--------------------------------------------------------------------------------
1 | // /**
2 | // * @module mrjsExtras
3 | // * @description mrjsExtras module acts as a one stop shop for all extras of mrjs. Ie things that are part of it for now but arent part of the main library.
4 | // * Items can be grabbed by importing as `import { class } from 'mrjs';` like any other class in mrjs.
5 | // */
6 |
7 | // // TODO - this should auto grab instead of manually be updated as manual updates will create problems.
8 |
9 | import { Refractor } from './Refractor.js';
10 | import { Water, WaterSystem } from './Water.js';
11 |
12 | export { Refractor, Water, WaterSystem };
13 | export { WaterRefractionShader } from 'three/examples/jsm/shaders/WaterRefractionShader.js';
14 |
--------------------------------------------------------------------------------
/src/global.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @global
3 | * @type {number}
4 | * @description the noted viewport height
5 | */
6 | global.viewPortHeight = 0;
7 |
8 | /**
9 | * @global
10 | * @type {number}
11 | * @description the noted viewport width
12 | */
13 | global.viewPortWidth = 0;
14 |
15 | /**
16 | * @global
17 | * @type {number}
18 | * @description UI needs to be scaled down in XR, 1:1 scale is huuuuge
19 | */
20 | global.XRScale = 1 / 2;
21 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module mrjs
3 | * @description The one-stop-shop module for mrjs (including an import for mrjsUtils). Includes the ability to access threejs directly as well.
4 | */
5 |
6 | // TODO - this should auto grab instead of manually be updated as manual updates will create problems.
7 | // for import and export
8 |
9 | import * as THREE from 'three';
10 |
11 | // Log the version number
12 | import { version } from '../package.json';
13 | console.log('-- ᴍʀjs Current Versioning --\nᴍʀjs :', version, '( using threejs :', THREE.REVISION, ')');
14 |
15 | // STYLE
16 | import './defaultStyle.css';
17 |
18 | // GLOBAL
19 | import './global';
20 |
21 | // DATATYPES
22 | import './dataTypes/MRClippingGeometry';
23 | import './dataTypes/MRPlane';
24 | // DATAMANAGERS
25 | import './dataManagers/MRPlaneManager';
26 |
27 | // CORE
28 | import './core/MRApp';
29 | import './core/MRElement';
30 | import './core/MREntity';
31 | import './core/MRSystem';
32 | // CORE - USER
33 | import './core/user/MRHand';
34 | import './core/user/MRUser';
35 | // CORE - ENTITIES
36 | import './core/entities/MRButtonEntity';
37 | import './core/entities/MRDivEntity';
38 | import './core/entities/MRHyperlinkEntity';
39 | import './core/entities/MRImageEntity';
40 | import './core/entities/MRLightEntity';
41 | import './core/entities/MRMediaEntity';
42 | import './core/entities/MRModelEntity';
43 | import './core/entities/MRPanelEntity';
44 | import './core/entities/MRSkyBoxEntity';
45 | import './core/entities/MRStatsEntity';
46 | import './core/entities/MRTextEntity';
47 | import './core/entities/MRTextAreaEntity';
48 | import './core/entities/MRTextFieldEntity';
49 | import './core/entities/MRTextInputEntity';
50 | import './core/entities/MRVideoEntity';
51 | import './core/entities/MRVolumeEntity';
52 | // CORE - COMPONENT-SYSTEMS
53 | import './core/componentSystems/AnchorSystem';
54 | import './core/componentSystems/AnimationSystem';
55 | import './core/componentSystems/BoundaryVisibilitySystem';
56 | import './core/componentSystems/ClippingSystem';
57 | import './core/componentSystems/ControlSystem';
58 | import './core/componentSystems/GeometryStyleSystem';
59 | import './core/componentSystems/InstancingSystem';
60 | import './core/componentSystems/LayoutSystem';
61 | import './core/componentSystems/MaskingSystem';
62 | import './core/componentSystems/MaterialStyleSystem';
63 | import './core/componentSystems/PhysicsSystem';
64 | import './core/componentSystems/SkyBoxSystem';
65 | import './core/componentSystems/StatsSystem';
66 | import './core/componentSystems/TextSystem';
67 |
68 | // EXPORTS
69 |
70 | // THREE - So users dont need a separate versioning import for it.
71 | export * as THREE from 'three';
72 |
73 | // MRJS - Exporting only necessary items for users to overwrite as they use MRjs.
74 | export * from 'mrjs/core/MRSystem';
75 | export * from 'mrjs/core/MREntity';
76 | export * from 'mrjs/core/entities/MRButtonEntity';
77 | export * from 'mrjs/core/entities/MRDivEntity';
78 | export * from 'mrjs/core/entities/MRHyperlinkEntity';
79 | export * from 'mrjs/core/entities/MRImageEntity';
80 | export * from 'mrjs/core/entities/MRLightEntity';
81 | export * from 'mrjs/core/entities/MRMediaEntity';
82 | export * from 'mrjs/core/entities/MRModelEntity';
83 | export * from 'mrjs/core/entities/MRPanelEntity';
84 | export * from 'mrjs/core/entities/MRSkyBoxEntity';
85 | export * from 'mrjs/core/entities/MRTextAreaEntity';
86 | export * from 'mrjs/core/entities/MRTextEntity';
87 | export * from 'mrjs/core/entities/MRTextFieldEntity';
88 | export * from 'mrjs/core/entities/MRVideoEntity';
89 | export * from 'mrjs/core/entities/MRVolumeEntity';
90 |
91 | // EXTRAS
92 | export * from './extras/index.js';
93 |
94 | // UTILS - exporting as a named group since it's a submodule of this js module
95 | export { mrjsUtils } from './utils/index.js';
96 |
--------------------------------------------------------------------------------
/src/utils/App.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @namespace app
3 | * @description Useful namespace for helping with app utility functions.
4 | */
5 | let app = {
6 | get scale() {
7 | if (mrjsUtils.xr.isPresenting) {
8 | return 0.5;
9 | }
10 | return 1;
11 | },
12 | };
13 |
14 | export { app };
15 |
--------------------------------------------------------------------------------
/src/utils/CSS.js:
--------------------------------------------------------------------------------
1 | import { warning } from 'mrjsUtils/Notify';
2 |
3 | /**
4 | * @namespace css
5 | * @description Useful namespace for helping with CSS utility functions
6 | */
7 | let css = {};
8 |
9 | let rootStyle = undefined;
10 | css.getVarFromRoot = function (str) {
11 | if (!rootStyle) {
12 | const root = document.documentElement;
13 | rootStyle = getComputedStyle(root);
14 | if (!rootStyle) {
15 | mrjsUtils.warning.warn('Bad var value. Tracked as css-variable, but no `:root` setup found:', str);
16 | return;
17 | }
18 | }
19 |
20 | return rootStyle.getPropertyValue(str).trim();
21 | };
22 |
23 | css.extractNumFromPixelStr = function (str) {
24 | const result = str.match(/(\d+)px/);
25 | return result ? parseInt(result[1]) : null;
26 | };
27 |
28 | /**
29 | * @function
30 | * @description Converts the dom string to a 3D numerical value
31 | * @param {string} val - the dom css information includes items of the form `XXXpx`, `XXX%`, etc
32 | * @returns {number} - the 3D numerical represenation of the dom css value
33 | */
34 | css.domToThree = function (val) {
35 | if (typeof val === 'string') {
36 | const valuepair = val.split(/(\d+(?:\.\d+)?)/).filter(Boolean);
37 | if (valuepair.length > 1) {
38 | switch (valuepair[1]) {
39 | case 'px':
40 | if (mrjsUtils.xr.isPresenting) {
41 | return val.split('px')[0] / global.appWidth;
42 | }
43 | return (val.split('px')[0] / global.appWidth) * global.viewPortWidth;
44 | case '%':
45 | if (mrjsUtils.xr.isPresenting) {
46 | return parseFloat(val) / 100;
47 | }
48 | return (parseFloat(val) / 100) * global.viewPortWidth;
49 | default:
50 | return val;
51 | }
52 | }
53 | }
54 | return val;
55 | };
56 |
57 | /**
58 | * @function
59 | * @memberof css
60 | * @description Converts 3D world positions to display positions based on global viewPort information.
61 | * Useful as part of the layout system and css value handling (px<-->threejs).
62 | * @param {number} val - the 3D value to be converted to 2D pixel space
63 | * @returns {number} - the 2D pixel space representation of value.
64 | */
65 | css.threeToPx = function (val) {
66 | return (val / global.viewPortHeight) * global.appHeight;
67 | };
68 |
69 | /**
70 | * @function
71 | * @memberof css
72 | * @description Converts display positions to 3D world positions to based on global viewPort information.
73 | * Useful as part of the layout system and css value handling (px<-->threejs).
74 | * @param {number} val - the 2D pixel space value to be converted to 3D space.
75 | * @returns {number} - the 3D representation of value.
76 | */
77 | css.pxToThree = function (val) {
78 | let px = val instanceof String ? val.split('px')[0] : val;
79 |
80 | if (mrjsUtils.xr.isPresenting) {
81 | return px / global.appWidth;
82 | }
83 | return (px / global.appWidth) * global.viewPortWidth;
84 | };
85 |
86 | export { css };
87 |
--------------------------------------------------------------------------------
/src/utils/Color.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @namespace color
3 | * @description Useful namespace for helping with color utility functions
4 | */
5 | let color = {};
6 |
7 | /**
8 | * @function
9 | * @memberof color
10 | * @param {string} hex - the hex code including "#" at the beginning
11 | * @description Converts a hex code into a usable rgba object value
12 | * @returns {object} - the calculated rgba value representation of the hex code
13 | * {
14 | * r: number, // Red component (0-255)
15 | * g: number, // Green component (0-255)
16 | * b: number, // Blue component (0-255)
17 | * a: number // Alpha component (0-1 for transparency)
18 | * }
19 | */
20 | color.hexToRgba = function (hex) {
21 | let r = 0,
22 | g = 0,
23 | b = 0,
24 | a = 1; // Default is black
25 | if (hex.startsWith('#')) {
26 | hex = hex.substring(1);
27 | }
28 |
29 | if (hex.length === 3) {
30 | r = parseInt(hex[0] + hex[0], 16);
31 | g = parseInt(hex[1] + hex[1], 16);
32 | b = parseInt(hex[2] + hex[2], 16);
33 | } else if (hex.length === 6) {
34 | r = parseInt(hex.substring(0, 2), 16);
35 | g = parseInt(hex.substring(2, 4), 16);
36 | b = parseInt(hex.substring(4, 6), 16);
37 | } else if (hex.length === 4) {
38 | r = parseInt(hex[0] + hex[0], 16);
39 | g = parseInt(hex[1] + hex[1], 16);
40 | b = parseInt(hex[2] + hex[2], 16);
41 | a = parseInt(hex[3] + hex[3], 16) / 255;
42 | } else if (hex.length === 8) {
43 | r = parseInt(hex.substring(0, 2), 16);
44 | g = parseInt(hex.substring(2, 4), 16);
45 | b = parseInt(hex.substring(4, 6), 16);
46 | a = parseInt(hex.substring(6, 8), 16) / 255;
47 | }
48 | return { r, g, b, a };
49 | };
50 |
51 | color.setObject3DColor = function (object3D, new_color, compStyle_opacity = '1', default_color = '#000') {
52 | const setColor = (object3D, new_color, compStyle_opacity, default_color) => {
53 | if (new_color.startsWith('rgba')) {
54 | const rgba = new_color
55 | .match(/rgba?\(([^)]+)\)/)[1]
56 | .split(',')
57 | .map((n) => parseFloat(n.trim()));
58 | object3D.material.color.setStyle(`rgb(${rgba[0]}, ${rgba[1]}, ${rgba[2]})`);
59 | object3D.material.transparent = rgba.length === 4 && rgba[3] < 1;
60 | object3D.material.opacity = rgba.length === 4 ? rgba[3] : 1;
61 | object3D.visible = !(rgba.length === 4 && rgba[3] === 0);
62 | } else if (new_color.startsWith('rgb')) {
63 | // RGB colors are treated as fully opaque
64 | object3D.material.color.setStyle(new_color);
65 | object3D.material.transparent = false;
66 | object3D.visible = true;
67 | } else if (new_color.startsWith('#')) {
68 | const { r, g, b, a } = mrjsUtils.color.hexToRgba(new_color);
69 | object3D.material.color.setStyle(`rgb(${r}, ${g}, ${b})`);
70 | object3D.material.transparent = a < 1;
71 | object3D.material.opacity = a;
72 | object3D.visible = a !== 0;
73 | } else {
74 | // This assumes the color is a CSS color word or another valid CSS color value
75 | object3D.material.color.setStyle(new_color ?? default_color);
76 | object3D.material.transparent = false;
77 | object3D.visible = true;
78 | }
79 | if (compStyle_opacity < 1) {
80 | object3D.material.opacity = compStyle_opacity;
81 | }
82 | object3D.material.needsUpdate = true;
83 | };
84 |
85 | if (object3D.isGroup) {
86 | mrjsUtils.warn.warn("setObject3DColor will not handle groups as expected, please use 'setEntityColor' instead.");
87 | } else {
88 | setColor(object3D, new_color, compStyle_opacity, default_color);
89 | }
90 | };
91 |
92 | color.setEntityOpacity = function (object3D, compStyle_opacity) {
93 | entity.traverseObjects((object) => {
94 | if (object.isMesh) {
95 | mrjsUtils.color.setObject3DOpacity(object, compStyle_opacity);
96 | }
97 | });
98 | };
99 |
100 | color.setObject3DOpacity = function (object3D, compStyle_opacity) {
101 | const setOpacity = (object3D, compStyle_opacity) => {
102 | if (compStyle_opacity <= 1) {
103 | object3D.material.opacity = compStyle_opacity;
104 | }
105 | object3D.material.needsUpdate = true;
106 | };
107 |
108 | if (object3D.isGroup) {
109 | mrjsUtils.warn.warn("setObject3DOpacity will not handle groups as expected, please use 'setEntityOpacity' instead.");
110 | } else {
111 | setOpacity(object3D, compStyle_opacity);
112 | }
113 | };
114 |
115 | color.setEntityColor = function (entity, new_color, compStyle_opacity = '1', default_color = '#000') {
116 | entity.traverseObjects((object) => {
117 | if (object.isMesh) {
118 | mrjsUtils.color.setObject3DColor(object, new_color, compStyle_opacity, default_color);
119 | }
120 | });
121 | };
122 |
123 | export { color };
124 |
--------------------------------------------------------------------------------
/src/utils/Display.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @namespace display
3 | * @description Useful namespace for helping with Display utility functions
4 | */
5 | let display = {};
6 |
7 | // const _VIRTUAL_DISPLAY_RESOLUTION = 1080;
8 | /**
9 | * @memberof display
10 | * @description Defaults to 1080;
11 | */
12 | display.VIRTUAL_DISPLAY_RESOLUTION = 1080; //alert(_VIRTUAL_DISPLAY_RESOLUTION);
13 |
14 | /**
15 | * @function
16 | * @memberof display
17 | * @description Checks whether the user is on mobile or not based on a large list of potential options.
18 | * @returns {boolean} - returns true if on any mobile devices.
19 | */
20 | display.mobileCheckFunction = function () {
21 | let userAgent = navigator.userAgent || navigator.vendor || window.opera;
22 | const userAgentRegex0 = new RegExp(
23 | '(android|bbd+|meego).+mobile|avantgo|bada/|blackberry|blazer|\
24 | compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |\
25 | maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|\
26 | phone|p(ixi|re)/|plucker|pocket|psp|series(4|6)0|symbian|treo|\
27 | up.(browser|link)|vodafone|wap|windows ce|xda|xiino',
28 | 'is'
29 | );
30 | const userAgentRegex1 = new RegExp(
31 | '/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|\
32 | ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|\
33 | attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|\
34 | bumb|bw-(n|u)|c55/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|\
35 | craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|\
36 | el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|\
37 | g1 u|g560|gene|gf-5|g-mo|go(.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|\
38 | hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|\
39 | i-(20|go|ma)|i230|iac( |-|/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|\
40 | iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |/)|klon|kpt |kwc-|\
41 | kyo(c|k)|le(no|xi)|lg( g|/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|\
42 | m3ga|m50/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|\
43 | mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|\
44 | n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|\
45 | nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|\
46 | phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|\
47 | qtek|r380|r600|raks|rim9|ro(ve|zo)|s55/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|\
48 | sdk/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|\
49 | so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|\
50 | tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|\
51 | up(.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|\
52 | voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|\
53 | wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-',
54 | 'is'
55 | );
56 | return userAgentRegex0.test(userAgent) || userAgentRegex1.test(userAgent.substr(0, 4));
57 | };
58 |
59 | export { display };
60 |
--------------------------------------------------------------------------------
/src/utils/Geometry.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | /**
4 | * @namespace geometry
5 | * @description Useful namespace for helping with geometry utility functions
6 | */
7 | let geometry = {};
8 |
9 | /**
10 | * @function
11 | * @memberof geometry
12 | * @description This construction function creates the UIPlane that is used as the backdrop for most mrjs Panel divs.
13 | * @param {number} width - the expected width of the plane.
14 | * @param {number} height - the expected height of the plane.
15 | * @param {number} radius_corner - the expected radius value to curve the planes corners.
16 | * @param {number} smoothness - the expected smoothness value.
17 | * @returns {THREE.BufferGeometry} - The completed threejs plane object.
18 | */
19 | geometry.UIPlane = function (width, height, radius_corner, smoothness) {
20 | let w = width == 'auto' ? 1 : width;
21 | w = w != 0 ? w : 1;
22 | let h = height == 'auto' ? 1 : height;
23 | h = h != 0 ? h : 1;
24 | const r = radius_corner[0] == 0 ? 0.0001 : radius_corner[0];
25 | const s = smoothness; // shortening for calculation quickness.
26 |
27 | if (!w || !h || !r || !s) {
28 | return null;
29 | }
30 |
31 | // helper const's
32 | const wi = w / 2 - r; // inner width
33 | const hi = h / 2 - r; // inner height
34 | const ul = r / w; // u left
35 | const ur = (w - r) / w; // u right
36 | const vl = r / h; // v low
37 | const vh = (h - r) / h; // v high
38 |
39 | const positions = [wi, hi, 0, -wi, hi, 0, -wi, -hi, 0, wi, -hi, 0];
40 |
41 | const uvs = [ur, vh, ul, vh, ul, vl, ur, vl];
42 |
43 | const n = [3 * (s + 1) + 3, 3 * (s + 1) + 4, s + 4, s + 5, 2 * (s + 1) + 4, 2, 1, 2 * (s + 1) + 3, 3, 4 * (s + 1) + 3, 4, 0];
44 |
45 | const indices = [n[0], n[1], n[2], n[0], n[2], n[3], n[4], n[5], n[6], n[4], n[6], n[7], n[8], n[9], n[10], n[8], n[10], n[11]];
46 |
47 | let phi;
48 | let cos;
49 | let sin;
50 | let xc;
51 | let yc;
52 | let uc;
53 | let vc;
54 | let idx;
55 |
56 | for (let i = 0; i < 4; i++) {
57 | xc = i < 1 || i > 2 ? wi : -wi;
58 | yc = i < 2 ? hi : -hi;
59 |
60 | uc = i < 1 || i > 2 ? ur : ul;
61 | vc = i < 2 ? vh : vl;
62 |
63 | for (let j = 0; j <= s; j++) {
64 | phi = (Math.PI / 2) * (i + j / s);
65 | cos = Math.cos(phi);
66 | sin = Math.sin(phi);
67 |
68 | positions.push(xc + r * cos, yc + r * sin, 0);
69 |
70 | uvs.push(uc + ul * cos, vc + vl * sin);
71 |
72 | if (j < s) {
73 | idx = (s + 1) * i + j + 4;
74 | indices.push(i, idx, idx + 1);
75 | }
76 | }
77 | }
78 |
79 | const geo = new THREE.BufferGeometry();
80 | geo.setIndex(new THREE.BufferAttribute(new Uint32Array(indices), 1));
81 | geo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3));
82 | geo.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvs), 2));
83 | geo.computeBoundingBox();
84 | geo.computeVertexNormals();
85 |
86 | geo.name = 'uiPlane';
87 |
88 | return geo;
89 | };
90 |
91 | export { geometry };
92 |
--------------------------------------------------------------------------------
/src/utils/HTML.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @namespace html
3 | * @description Useful namespace for helping with html utility functions
4 | */
5 | let html = {};
6 |
7 | /**
8 | * @function
9 | * @memberof html
10 | * @param {string} path - either a relative or full path inputted to an element. This can also be a path that has items separated by ',' so
11 | * that you can resolve multiple items at once, since we allow users to send us multiple files that way.
12 | * @param {string} baseUrl - a separate entry for if you want your url to start differently. this defaults to your window.location.origin.
13 | * Additionally removes all queries from the end of the url, leaving the input as just the origin and its pathname.
14 | * For ex: 'https://example.com/images/photo.png?version=2' becomes 'https://example.com/images/photo.png'
15 | * @description Given the path returns an absolute path resolved so relative linking works as expected.
16 | * @returns {string} a.href - the absolute path (or paths)
17 | */
18 | html.resolvePath = function (path, baseUrl = window.location.origin) {
19 | const fixPath = (path, baseUrl) => {
20 | let a = document.createElement('a');
21 | a.href = html.removeUrlQueries(path, baseUrl);
22 | return a.href;
23 | };
24 |
25 | // Handle multiple paths separated by commas
26 | if (path.includes(',')) {
27 | return path
28 | .split(',')
29 | .map((p) => fixPath(p.trim()))
30 | .join(',');
31 | }
32 |
33 | // singular path
34 | return fixPath(path, baseUrl);
35 | };
36 |
37 | /**
38 | * @function
39 | * @memberof html
40 | * @param {string} path - either a relative or full path inputted to an element.
41 | * @param {string} baseUrl - a separate entry for if you want your url to start differently. this defaults to your window.location.origin.
42 | * @description Removes all queries from the end of the url, leaving the input as just the origin and its pathname.
43 | * For ex: 'https://example.com/images/photo.png?version=2' becomes 'https://example.com/images/photo.png'
44 | * @returns {string} a.href - the absolute path
45 | */
46 | html.removeUrlQueries = function (path, baseUrl = window.location.origin) {
47 | try {
48 | // Check if path is absolute. If not, use baseUrl as the second parameter
49 | let urlObj = new URL(path, baseUrl);
50 | return urlObj.origin + urlObj.pathname;
51 | } catch (error) {
52 | console.warn('Error processing URL:', error.message);
53 | return path; // Return the original path if there's an error
54 | }
55 | };
56 |
57 | export { html };
58 |
--------------------------------------------------------------------------------
/src/utils/JS.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | /**
4 | * @namespace js
5 | * @description Useful namespace for helping with common needed JS quick functions
6 | */
7 | let js = {};
8 |
9 | /**
10 | * @function
11 | * @memberof js
12 | * @param {object} instance - the object whose class is being checked
13 | * @param {object} BaseClass - the given name of the BaseClass being checked against. Not in quotes.
14 | * @example JS.isInstanceOfBaseClassOnly(entity, MRDivEntity) would return true only on entities.
15 | * @description Given the parent, grabs either the parent's direct material or (in the case of a group) the
16 | * material of the first child hit.
17 | * @returns {object} material - the grabbed material
18 | */
19 | js.isInstanceOfBaseClassOnly = function (instance, BaseClass) {
20 | return instance.constructor === BaseClass;
21 | };
22 |
23 | js.applyAttributes = function (object, attribMap) {
24 | Object.entries(attributeMap).forEach(([key, value]) => {
25 | if (key in object) {
26 | object[key] = value;
27 | }
28 | });
29 | };
30 |
31 | js.isVariableDeclared = function (myVar) {
32 | return typeof myVar !== 'undefined';
33 | };
34 |
35 | export { js };
36 |
--------------------------------------------------------------------------------
/src/utils/Material.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import { html } from 'mrjsUtils/HTML';
3 |
4 | /**
5 | * @namespace material
6 | * @description Useful namespace for helping with Materials and threejs utility functions
7 | */
8 | let material = {};
9 |
10 | /**
11 | * @function
12 | * @memberof material
13 | * @param {object} parent - either a THREE.Group or a THREE.mesh/object
14 | * @description Given the parent, grabs either the parent's direct material or (in the case of a group) the
15 | * material of the first child hit.
16 | * @returns {object} material - the grabbed material
17 | */
18 | material.getObjectMaterial = function (parent) {
19 | let foundMesh = false;
20 | let material;
21 |
22 | if (parent instanceof THREE.Group) {
23 | parent.traverse((child) => {
24 | if (!foundMesh && child instanceof THREE.Mesh) {
25 | material = child.material;
26 | foundMesh = true;
27 | }
28 | });
29 | } else {
30 | material = parent.material;
31 | }
32 |
33 | return material;
34 | };
35 |
36 | /**
37 | * @function
38 | * @memberof material
39 | * @param {object} parent - either a THREE.Group or a THREE.mesh/object
40 | * @param {object} material - a threejs material to be set for either the parent's direct material or
41 | * (in the case of a group) the material of all children within the parent group.
42 | * @description Given the parent, grabs either the parents direct material or (in the case of a group) the
43 | * material of the first child hit.
44 | * @returns {object} parent - the updated parent object
45 | */
46 | material.setObjectMaterial = function (parent, material) {
47 | if (parent instanceof THREE.Group) {
48 | parent.traverse((child) => {
49 | if (child instanceof THREE.Mesh) {
50 | child.material = material;
51 | child.material.needsUpdate = true;
52 | }
53 | });
54 | } else {
55 | parent.material = material;
56 | parent.material.needsUpdate = true;
57 | }
58 | return parent;
59 | };
60 |
61 | /**
62 | * @function
63 | * @memberof material
64 | * @param {object} src - the url path to the data to be loaded
65 | * @description Function to load the texture asynchronously and return a promise
66 | * @returns {object} texture - the fully loaded texture
67 | */
68 | material.loadTextureAsync = function (src) {
69 | return new Promise((resolve, reject) => {
70 | const textureLoader = new THREE.TextureLoader();
71 |
72 | let resolvedSrc = html.resolvePath(src);
73 |
74 | // Use the img's src to load the texture
75 | textureLoader.load(
76 | resolvedSrc,
77 | (texture) => {
78 | resolve(texture);
79 | },
80 | undefined,
81 | (error) => {
82 | reject(error);
83 | }
84 | );
85 | });
86 | };
87 |
88 | /**
89 | * @function
90 | * @memberof material
91 | * @param {object} video - the html video element whose src contains the path to the data to be loaded
92 | * @description Function to load the texture asynchronously and return a promise
93 | * @returns {object} texture - the fully loaded texture
94 | */
95 | material.loadVideoTextureAsync = function (video) {
96 | video.src = html.resolvePath(video.src);
97 |
98 | video.muted = true; // Mute the video to allow autoplay
99 | video.autoplay = false; //true; // Attempt to autoplay
100 |
101 | return new Promise((resolve, reject) => {
102 | // Event listener to ensure video is ready
103 | video.onloadeddata = () => {
104 | const videoTexture = new THREE.VideoTexture(video);
105 | videoTexture.needsUpdate = true; // Ensure the texture updates when the video plays
106 |
107 | video
108 | .play()
109 | .then(() => {
110 | console.log('Video playback started');
111 | resolve(videoTexture);
112 | })
113 | .catch((e) => {
114 | console.error('Error trying to play the video:', e);
115 | reject(e);
116 | });
117 | };
118 |
119 | video.onerror = (error) => {
120 | reject(new Error('Error loading video: ' + error.message));
121 | };
122 |
123 | // This can help with ensuring the video loads in some cases
124 | video.load();
125 | });
126 | };
127 |
128 | export { material };
129 |
--------------------------------------------------------------------------------
/src/utils/Math.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | /**
4 | * @namespace math
5 | * @description Useful namespace for helping with Math utility functions including numerical, 3d, etc.
6 | */
7 | let math = {};
8 |
9 | /**************************/
10 | /***** NUMERICAL MATH *****/
11 | /**************************/
12 |
13 | /**
14 | * @function
15 | * @memberof math
16 | * @description Rounds the inputted val to the nearest decimal place as denoted by the decimal parameter.
17 | * @example For example: roundTo(832.456, 10) = 832.4; roundTo(832.456, 1000) = 832.456; roundTo(832.456, 0.01) = 800;
18 | * @param {number} val - The number to be rounded.
19 | * @param {number} decimal - The decimal place targeted in the rounding.
20 | * @returns {number} - The rounded number to the requested decimal amount.
21 | */
22 | math.roundTo = function (val, decimal) {
23 | return Math.round(val * decimal) / decimal;
24 | };
25 |
26 | /**
27 | * @function
28 | * @memberof math
29 | * @description Rounds the inputted vector to the nearest decimal place as denoted by the decimal parameter.
30 | * @example For example: roundTo(<832.456, 92.10003, 23452.1>, 10) = <832.4, 92.1, 2342.1>;
31 | * @param {vector} vector - The vector of numbers to be rounded.
32 | * @param {number} decimal - The decimal place targeted in the rounding.
33 | */
34 | math.roundVectorTo = function (vector, decimal) {
35 | vector.multiplyScalar(decimal);
36 | vector.roundToZero();
37 | vector.divideScalar(decimal);
38 | };
39 |
40 | /**
41 | * @function
42 | * @memberof math
43 | * @description Performs the radian To Degree calculation commonly used in math.
44 | * https://en.wikipedia.org/wiki/Degree_(angle) https://en.wikipedia.org/wiki/Radian
45 | * @param {number} val - The number to be converted from radians to degrees
46 | * @returns {number} - the calculated degree representation of val.
47 | */
48 | math.radToDeg = function (val) {
49 | return (val * Math.PI) / 180;
50 | };
51 |
52 | /*******************/
53 | /***** 3D MATH *****/
54 | /*******************/
55 |
56 | /**
57 | * @function
58 | * @memberof math
59 | * @description Computes the bounding sphere of an inputted three group object.
60 | * @param {THREE.group} group - the group to be enclosed in the bounding sphere.
61 | * @param {THREE.group} relativeTo - object that the group is relative to. For example if the group is an apple held in a
62 | * character's hand, relativeTo would be the characters hand. When left as null, the bounding sphere defaults to the inputted groups original world matrix.
63 | * @returns {THREE.Sphere} - the resolved bounding sphere
64 | */
65 | math.computeBoundingSphere = function (group, relativeTo = null) {
66 | let sphere = new THREE.Sphere();
67 | let box = new THREE.Box3();
68 |
69 | box.setFromObject(group);
70 | box.getBoundingSphere(sphere);
71 |
72 | sphere.applyMatrix4(relativeTo ? relativeTo.matrixWorld : group.matrixWorld);
73 |
74 | return sphere;
75 | };
76 |
77 | export { math };
78 |
--------------------------------------------------------------------------------
/src/utils/Notify.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @namespace error
3 | * @description Useful namespace for helping with error utility functions
4 | */
5 | let error = {};
6 |
7 | /**
8 | * @function
9 | * @memberof error
10 | * @description Function helper to error in console if a child class is expected to overwrite a parent
11 | * class's function but didnt.
12 | */
13 | error.emptyParentFunction = function () {
14 | console.error('Empty parent function was reached; this must be overridden in children.');
15 | };
16 |
17 | /**
18 | * @function
19 | * @param {string} string - string of texted emitted through the console.
20 | * @memberof error
21 | * @description Function helper separated out to console error for when we eventually have a more robust
22 | * erroring system.
23 | */
24 | error.err = function (string) {
25 | console.error(string);
26 | };
27 |
28 | /**
29 | * @namespace warn
30 | * @description Useful namespace for helping with error utility functions
31 | */
32 | let warn = {};
33 |
34 | /**
35 | * @function
36 | * @memberof warn
37 | * @description Function helper to warn in console if a child class might want to overwrite a parent
38 | * class's function but didnt. Useful for base classes that are more abstract classes (if in Java or C++)
39 | * to remind the user of the child class that there is more to implement.
40 | */
41 | warn.EmptyParentFunction = function () {
42 | console.warn('Empty parent function was reached, make sure this was overridden in children if more execution was expected.');
43 | };
44 |
45 | /**
46 | * @function
47 | * @param {string} string - string of texted emitted through the console.
48 | * @memberof warn
49 | * @description Function helper separated out to console warn for when we eventually have a more robust
50 | * warning system.
51 | */
52 | warn.warn = function (string) {
53 | console.warn(string);
54 | };
55 |
56 | export { error, warn };
57 |
--------------------------------------------------------------------------------
/src/utils/Physics.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @namespace physics
3 | * @description Useful namespace for helping with physics utility functions
4 | */
5 | let physics = {};
6 |
7 | /**
8 | * @memberof physics
9 | * @description the Rapier collision groups used throughout MRjs
10 | */
11 | physics.CollisionGroups = {
12 | USER: 0x00020001,
13 | PLANES: 0x0004ffff,
14 | UI: 0x00010002,
15 | };
16 |
17 | /**
18 | * @memberof physics
19 | * @description the RAPIER physics controller object
20 | */
21 | physics.RAPIER = null;
22 |
23 | physics.initialized = false;
24 |
25 | physics.initializePhysics = async function () {
26 | if (!physics.initialized) {
27 | physics.RAPIER = await import('@dimforge/rapier3d');
28 | physics.initialized = true;
29 | physics.eventQueue = new physics.RAPIER.EventQueue(true);
30 | physics.world = new physics.RAPIER.World({ x: 0.0, y: -9.81, z: 0.0 });
31 |
32 | document.dispatchEvent(new CustomEvent('engine-started', { bubbles: true }));
33 | }
34 | return physics;
35 | };
36 |
37 | // const _INPUT_COLLIDER_HANDLE_NAMES = {};
38 | /**
39 | * @memberof physics
40 | * @description the Rapier INPUT_COLLIDER_HANDLE_NAMES
41 | */
42 | physics.INPUT_COLLIDER_HANDLE_NAMES = {}; //alert(_INPUT_COLLIDER_HANDLE_NAMES);
43 |
44 | // const _COLLIDER_ENTITY_MAP = {};
45 | /**
46 | * @memberof physics
47 | * @description the Rapier COLLIDER_ENTITY_MAP
48 | */
49 | physics.COLLIDER_ENTITY_MAP = {};
50 |
51 | export { physics };
52 |
--------------------------------------------------------------------------------
/src/utils/String.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @namespace string
3 | * @description Useful namespace for helping with String utility functions
4 | */
5 | let string = {};
6 |
7 | /*******************************************************/
8 | /************ JSON // String interactions **************/
9 | /*******************************************************/
10 |
11 | /**
12 | * @function
13 | * @memberof string
14 | * @description Converts and formats the inputted string to a json object.
15 | * @param {string} attrString - the string to be formatted
16 | * @returns {object} - object in json form
17 | */
18 | string.stringToJson = function (attrString) {
19 | if (attrString == null) {
20 | return null;
21 | }
22 | const regexPattern = /(\w+):\s*([^;]+)/g;
23 | const jsonObject = {};
24 |
25 | let match;
26 | while ((match = regexPattern.exec(attrString)) !== null) {
27 | const key = match[1].trim();
28 | let value = match[2].trim();
29 |
30 | // Check value type and convert if necessary
31 | if (value.includes(' ')) {
32 | value = value.split(' ').map((v) => parseFloat(v));
33 | } else if (/^\d+(\.\d+)?$/.test(value)) {
34 | value = parseFloat(value);
35 | } else if (value === 'true') {
36 | value = true;
37 | } else if (value === 'false') {
38 | value = false;
39 | }
40 |
41 | jsonObject[key] = value;
42 | }
43 |
44 | return jsonObject;
45 | };
46 |
47 | /**
48 | * @function
49 | * @memberof string
50 | * @description Converts and formats the inputted json object into a string.
51 | * @param {object} componentData - the json object to be formatted into a string
52 | * @returns {string} - the string representation of the json object
53 | */
54 | string.jsonToString = function (componentData) {
55 | let compString = '';
56 |
57 | for (const [key, value] of Object.entries(componentData)) {
58 | let stringValue;
59 |
60 | if (Array.isArray(value)) {
61 | // Convert array of numbers to space-separated string
62 | stringValue = value.join(' ');
63 | } else {
64 | // Use the value directly for numbers and booleans
65 | stringValue = value.toString();
66 | }
67 |
68 | // Append the key-value pair to the component string
69 | compString += `${key}: ${stringValue}; `;
70 | }
71 |
72 | return compString.trim();
73 | };
74 |
75 | /****************************************/
76 | /*********** String to Math *************/
77 | /****************************************/
78 |
79 | /**
80 | * @function
81 | * @memberof string
82 | * @description Converts a string to vector format.
83 | * @param {string} str - the string to be converted to a vector. Must be of format 'xx xxx xx...'.
84 | * @returns {object} - the vector version of the inputted string.
85 | */
86 | string.stringToVector = function (str) {
87 | return str?.split(' ').map(Number) ?? null;
88 | };
89 |
90 | string.vectorToString = function (arr) {
91 | let str = '';
92 | for (let i = 0; i < arr.length; ++i) {
93 | str += arr[i];
94 | if (i + 1 != arr.length) {
95 | str += ' ';
96 | }
97 | }
98 | return str;
99 | };
100 |
101 | /**
102 | * @function
103 | * @memberof string
104 | * @description Converts a string to vector format where the numbers are pre-converted from radians to degrees.
105 | * @param {string} str - the string to be converted to a vector. Must be of format 'xx xxx xx...'.
106 | * @returns {object} - the vector version of the inputted string.
107 | */
108 | string.stringToDegVector = function (str) {
109 | return str.split(' ').map((val) => (parseFloat(val) * Math.PI) / 180);
110 | };
111 |
112 | /**
113 | * @function
114 | * @memberof string
115 | * @description Converts a string to vector format where the numbers are pre-converted from a number to an appropriate representation
116 | * @param {string} val - the string to be converted to a vector. Must be of format 'x%' or 'x/y'.
117 | * @returns {number} - the vector version of the inputted string.
118 | */
119 | string.stringToDimensionValue = function (val) {
120 | if (val.includes('%')) {
121 | return parseFloat(val) / 100;
122 | }
123 | if (val.includes('/')) {
124 | return parseInt(val.split('/')[0]) / parseInt(val.split('/')[1]);
125 | }
126 | return val;
127 | };
128 |
129 | export { string };
130 |
--------------------------------------------------------------------------------
/src/utils/XR.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @namespace xr
3 | * @description Useful namespace for helping with xr utility functions.
4 | * this is set within the MRApp to access various WebXR API features
5 | */
6 | let xr = {};
7 |
8 | export { xr };
9 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | // /**
2 | // * @module mrjsUtils
3 | // * @description mrjsUtils module acts as a one stop shop for all global util functions of mrjs.
4 | // * Items can be grabbed by importing as `import { mrjsUtils } from 'mrjs';` and calling them through `mrjsUtils.ParentFile.functionName(...);`
5 | // */
6 |
7 | // // TODO - this should auto grab instead of manually be updated as manual updates will create problems.
8 |
9 | import { app } from './App.js';
10 | import { color } from './Color.js';
11 | import { css } from './CSS.js';
12 | import { display } from './Display.js';
13 | import { geometry } from './Geometry.js';
14 | import { html } from './HTML.js';
15 | import { js } from './JS.js';
16 | import { material } from './Material.js';
17 | import { math } from './Math.js';
18 | import { model } from './Model.js';
19 | import { error, warn } from './Notify.js';
20 | import { physics } from './Physics.js';
21 | import { string } from './String.js';
22 | import { xr } from './XR.js';
23 |
24 | const mrjsUtils = {
25 | app,
26 | color,
27 | css,
28 | display,
29 | error,
30 | geometry,
31 | html,
32 | js,
33 | material,
34 | math,
35 | model,
36 | physics,
37 | string,
38 | warn,
39 | xr,
40 | };
41 |
42 | export { mrjsUtils }; // Export as named export
43 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | import CopyPlugin from 'copy-webpack-plugin';
2 | import path, { dirname } from 'path';
3 | import { fileURLToPath } from 'url';
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | // Determine the environment (e.g., testing or development)
9 | const isTesting = process.env.NODE_ENV === 'development';
10 |
11 | export default {
12 | entry: {
13 | main: './src/index.js',
14 | },
15 | output: {
16 | filename: 'mr.js',
17 | path: path.resolve(__dirname, 'dist'),
18 | publicPath: 'auto',
19 | libraryTarget: 'window',
20 | },
21 |
22 | devServer: {
23 | https: true,
24 | static: {
25 | directory: path.join(__dirname, 'dist'),
26 | },
27 | client: {
28 | overlay: {
29 | errors: true,
30 | warnings: false,
31 | runtimeErrors: true,
32 | },
33 | },
34 | compress: true,
35 | },
36 |
37 | optimization: {
38 | minimize: false,
39 | },
40 |
41 | experiments: {
42 | asyncWebAssembly: true,
43 | },
44 |
45 | resolve: {
46 | extensions: ['.mjs', '.js'],
47 | alias: {
48 | mrjs: path.resolve(__dirname, './src'), // <-- When you build or restart dev-server, you'll get an error if the path to your global.js file is incorrect.
49 | mrjsUtils: path.resolve(__dirname, './src/utils'),
50 | },
51 | fallback: {
52 | fs: false,
53 | path: false,
54 | },
55 | fullySpecified: false, // disable required .js / .mjs extensions when importing
56 | },
57 |
58 | mode: process.env.NODE_ENV || 'development',
59 |
60 | plugins: [
61 | new CopyPlugin({
62 | patterns: [
63 | // make these items generate in dist as default for the runner: index.html, style.css, and assets folder
64 | { from: 'samples/index-assets', to: 'index-assets' },
65 | { from: 'samples/index.html', to: 'index.html' },
66 | { from: 'samples/index-style.css', to: 'index-style.css' },
67 | { from: 'samples/examples', to: 'examples' },
68 | { from: 'samples/examples-assets', to: 'examples-assets' },
69 | ],
70 | }),
71 | ],
72 |
73 | module: {
74 | rules: [
75 | {
76 | test: /\.m?js/,
77 | type: 'javascript/auto',
78 | },
79 | {
80 | test: /\.m?js/,
81 | resolve: {
82 | fullySpecified: false,
83 | },
84 | },
85 | {
86 | test: /\.wasm$/,
87 | type: 'webassembly/async', // or 'webassembly/sync'
88 | },
89 | {
90 | test: /\.css$/,
91 | use: ['style-loader', 'css-loader'],
92 | },
93 | {
94 | test: /\.json$/,
95 | use: 'json-loader',
96 | },
97 | ],
98 | },
99 | };
100 |
--------------------------------------------------------------------------------