@miris-inc/components 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bus.ts ADDED
@@ -0,0 +1,33 @@
1
+ import type Primitive from "./primitive";
2
+ import type Scene from "./scene";
3
+
4
+ type Subscriber = Primitive | Scene;
5
+ type Callback = (...params: any[]) => void;
6
+
7
+ export default class Bus {
8
+ static #subscriptions = new Map<Subscriber, Map<string, Callback>>();
9
+
10
+ static subscribe(key: Subscriber, name: string, callback: Callback) {
11
+ if (!this.#subscriptions.has(key)) {
12
+ this.#subscriptions.set(key, new Map());
13
+ }
14
+
15
+ this.#subscriptions.get(key)!.set(name, callback);
16
+ }
17
+
18
+ static unsubscribe(key: Subscriber, name: string) {
19
+ if (!this.#subscriptions.has(key)) return;
20
+
21
+ this.#subscriptions.get(key)!.delete(name);
22
+
23
+ if (this.#subscriptions.get(key)!.size) {
24
+ this.#subscriptions.delete(key);
25
+ }
26
+ }
27
+
28
+ static publish(key: Subscriber, name: string, ...args: any[]) {
29
+ const callback = this.#subscriptions.get(key)?.get(name);
30
+
31
+ if (callback) callback(...args);
32
+ }
33
+ }
package/camera.ts ADDED
@@ -0,0 +1,67 @@
1
+ import { PerspectiveCamera as ThreeCamera } from "three";
2
+ import Primitive from "./primitive";
3
+
4
+ export default class MirisCamera extends Primitive {
5
+ #fov = 50;
6
+ // prettier-ignore
7
+ get fov() { return this.#fov }
8
+ set fov(fov) {
9
+ if (this._threeObject) {
10
+ this._threeObject.fov = fov;
11
+ this._threeObject.updateProjectionMatrix();
12
+ }
13
+
14
+ this.#fov = fov;
15
+ }
16
+
17
+ #aspect = 1;
18
+ // prettier-ignore
19
+ get aspect() { return this.#aspect }
20
+ set aspect(aspect) {
21
+ if (this._threeObject) {
22
+ this._threeObject.aspect = aspect;
23
+ this._threeObject.updateProjectionMatrix();
24
+ }
25
+
26
+ this.#aspect = aspect;
27
+ }
28
+
29
+ #near = 0.1;
30
+ // prettier-ignore
31
+ get near() { return this.#near }
32
+ set near(near) {
33
+ if (this._threeObject) {
34
+ this._threeObject.near = near;
35
+ this._threeObject.updateProjectionMatrix();
36
+ }
37
+
38
+ this.#near = near;
39
+ }
40
+
41
+ #far = 2000;
42
+ // prettier-ignore
43
+ get far() { return this.#far }
44
+ set far(far) {
45
+ if (this._threeObject) {
46
+ this._threeObject.far = far;
47
+ this._threeObject.updateProjectionMatrix();
48
+ }
49
+
50
+ this.#far = far;
51
+ }
52
+
53
+ protected override _threeObject: ThreeCamera | null = null;
54
+
55
+ override _enable() {
56
+ this._threeObject = new ThreeCamera(
57
+ this.fov,
58
+ this.aspect,
59
+ this.near,
60
+ this.far
61
+ );
62
+
63
+ this._threeObject.name = "supplied camera";
64
+
65
+ super._enable();
66
+ }
67
+ }
package/elements.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { default as Scene } from "./scene";
2
+ export { default as Camera } from "./camera";
3
+ export { default as Group } from "./group";
4
+ export { default as Stream } from "./stream";
package/group.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { Group as ThreeGroup } from "three";
2
+ import Primitive from "./primitive";
3
+
4
+ export default class MirisGroup extends Primitive {
5
+ protected override _threeObject: ThreeGroup | null = null;
6
+
7
+ override _enable() {
8
+ this._threeObject = new ThreeGroup();
9
+
10
+ super._enable();
11
+ }
12
+ }
package/index.html ADDED
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta http-equiv="refresh" content="0; url=./test/index.html">
6
+ <title>Redirecting...</title>
7
+ </head>
8
+ <body>
9
+ <p>Redirecting to <a href="./test/index.html">test page</a>...</p>
10
+ </body>
11
+ </html>
package/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ import * as elements from "./elements";
2
+
3
+ for (const name of [
4
+ "Scene",
5
+ "Camera",
6
+ "Group",
7
+ "Stream",
8
+ ] as (keyof typeof elements)[]) {
9
+ customElements.define(`miris-${name.toLowerCase()}`, elements[name]);
10
+ }
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@miris-inc/components",
3
+ "exports": "./index.ts",
4
+ "scripts": {
5
+ "d": "bun dev",
6
+ "dev": "vite",
7
+ "b": "vite build",
8
+ "build": "vite build"
9
+ },
10
+ "devDependencies": {
11
+ "@miris-inc/three": "*",
12
+ "@types/three": "^0.182.0",
13
+ "three": "^0.182.0"
14
+ },
15
+ "version": "0.0.1"
16
+ }
package/primitive.ts ADDED
@@ -0,0 +1,202 @@
1
+ import { Euler, type Object3D, Vector3 as ThreeVector3 } from "three";
2
+ import Bus from "./bus";
3
+ import type Scene from "./scene";
4
+ import type Group from "./group";
5
+
6
+ type Vector3 = {
7
+ x: number;
8
+ y: number;
9
+ z: number;
10
+ set: (x: number, y: number, z: number) => void;
11
+ };
12
+ export default class MirisPrimitive extends HTMLElement {
13
+ #position = new ThreeVector3();
14
+ #rotation = new Euler();
15
+ #scale = new ThreeVector3(1, 1, 1);
16
+ declare readonly position: Vector3;
17
+ declare readonly rotation: Vector3;
18
+ declare readonly scale: Vector3;
19
+
20
+ #zoom = 1;
21
+ // prettier-ignore
22
+ get zoom () { return this.#zoom }
23
+ set zoom(zoom) {
24
+ this.#zoom = zoom;
25
+ const { x, y, z } = this.scale;
26
+ this._threeObject?.scale.set(zoom * x, zoom * y, zoom * z);
27
+ }
28
+
29
+ protected _parent: Group | Scene | null = null;
30
+ protected _threeObject: Object3D | null = null;
31
+
32
+ #initPosition() {
33
+ const self = this;
34
+
35
+ // prettier-ignore
36
+ Object.defineProperty(this, "position", {
37
+ value: {
38
+ get x() { return self.#position.x },
39
+ set x(x) {
40
+ self.#position.x = x;
41
+ if (self._threeObject) self._threeObject.position.x = x;
42
+ },
43
+
44
+ get y() { return self.#position.y },
45
+ set y(y) {
46
+ self.#position.y = y;
47
+ if (self._threeObject) self._threeObject.position.y = y;
48
+ },
49
+
50
+ get z() { return self.#position.z },
51
+ set z(z) {
52
+ self.#position.z = z;
53
+ if (self._threeObject) self._threeObject.position.z = z;
54
+ },
55
+
56
+ set(x: number, y: number, z: number) {
57
+ self.#position.x = x;
58
+ self.#position.y = y;
59
+ self.#position.z = z;
60
+ self._threeObject?.position.set(x, y, z);
61
+ },
62
+ },
63
+ });
64
+ Object.freeze(this.position);
65
+
66
+ const position = this.getAttribute("position");
67
+ const [x = 0, y = 0, z = 0] = position?.split(" ")?.map(parseFloat) ?? [];
68
+ this.position.set(x, y, z);
69
+ }
70
+
71
+ #initRotation() {
72
+ const self = this;
73
+
74
+ // prettier-ignore
75
+ Object.defineProperty(this, "rotation", {
76
+ value: {
77
+ get x() { return self.#rotation.x },
78
+ set x(x) {
79
+ self.#rotation.x = x;
80
+ if (self._threeObject) self._threeObject.rotation.x = x;
81
+ },
82
+
83
+ get y() { return self.#rotation.y },
84
+ set y(y) {
85
+ self.#rotation.y = y;
86
+ if (self._threeObject) self._threeObject.rotation.y = y;
87
+ },
88
+
89
+ get z() { return self.#rotation.z },
90
+ set z(z) {
91
+ self.#rotation.z = z;
92
+ if (self._threeObject) self._threeObject.rotation.z = z;
93
+ },
94
+
95
+ set(x: number, y: number, z: number) {
96
+ self.#rotation.x = x;
97
+ self.#rotation.y = y;
98
+ self.#rotation.z = z;
99
+ self._threeObject?.rotation.set(x, y, z);
100
+ },
101
+ },
102
+ });
103
+ Object.freeze(this.rotation);
104
+
105
+ const rotation = this.getAttribute("rotation");
106
+ const [x = 0, y = 0, z = 0] = rotation?.split(" ")?.map(parseFloat) ?? [];
107
+ this.rotation.set(x, y, z);
108
+ }
109
+
110
+ #initScale() {
111
+ const self = this;
112
+
113
+ // prettier-ignore
114
+ Object.defineProperty(this, "scale", {
115
+ value: {
116
+ get x() { return self.#scale.x },
117
+ set x(x) {
118
+ self.#scale.x = x;
119
+ if (self._threeObject) self._threeObject.scale.x = self.zoom * x;
120
+ },
121
+
122
+ get y() { return self.#scale.y },
123
+ set y(y) {
124
+ self.#scale.y = y;
125
+ if (self._threeObject) self._threeObject.scale.y = self.zoom * y;
126
+ },
127
+
128
+ get z() { return self.#scale.z },
129
+ set z(z) {
130
+ self.#scale.z = z;
131
+ if (self._threeObject) self._threeObject.scale.z = self.zoom * z;
132
+ },
133
+
134
+ set(x: number, y: number, z: number) {
135
+ const { zoom } = self
136
+ self.#scale.x = x;
137
+ self.#scale.y = y;
138
+ self.#scale.z = z;
139
+ self._threeObject?.scale.set(zoom * x, zoom * y, zoom * z);
140
+ },
141
+ },
142
+ });
143
+ Object.freeze(this.scale);
144
+
145
+ this.#zoom = parseFloat(this.getAttribute("zoom") ?? `${this.#zoom}`);
146
+ const scale = this.getAttribute("scale");
147
+ const [x = 1, y = 1, z = 1] = scale?.split(" ")?.map(parseFloat) ?? [];
148
+ this.scale.set(x, y, z);
149
+ }
150
+
151
+ constructor() {
152
+ super();
153
+ this.#initPosition();
154
+ this.#initRotation();
155
+ this.#initScale();
156
+ }
157
+
158
+ protected _enable() {
159
+ if (!document.body.contains(this) || !this._threeObject) {
160
+ return;
161
+ }
162
+
163
+ const { position, rotation, scale, zoom } = this;
164
+ const { x: pX, y: pY, z: pZ } = position;
165
+ const { x: rX, y: rY, z: rZ } = rotation;
166
+ const { x: sX, y: sY, z: sZ } = scale;
167
+ this._threeObject.position.set(pX, pY, pZ);
168
+ this._threeObject.rotation.set(rX, rY, rZ);
169
+ this._threeObject.scale.set(zoom * sX, zoom * sY, zoom * sZ);
170
+
171
+ if (this._parent) {
172
+ Bus.publish(this._parent, "add", this._threeObject);
173
+ }
174
+ }
175
+
176
+ protected _disable() {
177
+ if (this._parent) Bus.publish(this._parent, "remove", this._threeObject);
178
+
179
+ this._threeObject = null;
180
+ }
181
+
182
+ connectedCallback() {
183
+ const selector = "miris-group, miris-scene";
184
+ this._parent = this.parentElement?.closest(selector) ?? null;
185
+
186
+ this._enable();
187
+
188
+ Bus.subscribe(this, "add", (object) => {
189
+ if (this._threeObject?.children.includes(object)) return;
190
+
191
+ this._threeObject?.add(object);
192
+ });
193
+ Bus.subscribe(this, "remove", (object) => {
194
+ this._threeObject?.remove(object);
195
+ });
196
+ }
197
+
198
+ disconnectedCallback() {
199
+ Bus.unsubscribe(this, "add");
200
+ Bus.unsubscribe(this, "remove");
201
+ }
202
+ }
package/scene.ts ADDED
@@ -0,0 +1,127 @@
1
+ import {
2
+ MirisControls as ThreeControls,
3
+ MirisScene as ThreeScene,
4
+ } from "@miris-inc/three";
5
+ import {
6
+ type Object3D,
7
+ PerspectiveCamera as ThreeCamera,
8
+ WebGLRenderer as ThreeRenderer,
9
+ } from "three";
10
+ import Bus from "./bus";
11
+
12
+ export default class MirisScene extends HTMLElement {
13
+ static observedAttributes = ["key"];
14
+
15
+ #renderer: ThreeRenderer | null = null;
16
+ #scene: ThreeScene | null = null;
17
+ #camera: ThreeCamera | null = null;
18
+ #observer: ResizeObserver | null = null;
19
+
20
+ // prettier-ignore
21
+ get camera() : ThreeCamera { return this.#camera }
22
+ // prettier-ignore
23
+ set camera(camera: ThreeCamera) { this.#camera = camera }
24
+
25
+ #key: string | null = null;
26
+ // prettier-ignore
27
+ get key() { return this.#key }
28
+ set key(key) {
29
+ if (key === this.#key) return;
30
+
31
+ this.#key = key;
32
+
33
+ if (key) this.setAttribute("key", key);
34
+ else this.removeAttribute("key");
35
+ }
36
+
37
+ connectedCallback() {
38
+ const camera = new ThreeCamera();
39
+ const renderer = new ThreeRenderer({ alpha: true });
40
+ const controls = new ThreeControls(null, camera, renderer.domElement);
41
+ const scene = new ThreeScene(this.key);
42
+ const observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
43
+ const entry = entries[entries.length - 1]!;
44
+ const width = entry.contentBoxSize[0]!.inlineSize;
45
+ const height = entry.contentBoxSize[0]!.blockSize;
46
+
47
+ renderer.setPixelRatio(devicePixelRatio);
48
+ renderer.setSize(width, height, false);
49
+
50
+ camera.aspect = width / height;
51
+ camera.updateProjectionMatrix();
52
+ });
53
+
54
+ const sheet = new CSSStyleSheet();
55
+ const shadow = this.attachShadow({ mode: "closed" });
56
+ sheet.insertRule(":host { display: block }");
57
+ sheet.insertRule("canvas { display: block; width: 100%; height: 100% }");
58
+ shadow.adoptedStyleSheets.push(sheet);
59
+ shadow.append(renderer.domElement);
60
+
61
+ observer.observe(this);
62
+ renderer.setAnimationLoop(() => renderer.render(scene, camera));
63
+ this.#renderer = renderer;
64
+ this.#scene = scene;
65
+ this.#observer = observer;
66
+ this.#camera = camera;
67
+
68
+ Bus.subscribe(this, "add", (object: Object3D) => {
69
+ if (scene.children.includes(object)) return;
70
+ if (object instanceof ThreeCamera) return;
71
+ scene.add(object);
72
+ });
73
+ Bus.subscribe(this, "remove", (object: Object3D) => {
74
+ scene.remove(object);
75
+ });
76
+ Bus.subscribe(this, "control", (object: Object3D) => {
77
+ controls.objects.add(object);
78
+ });
79
+ Bus.subscribe(this, "uncontrol", (object: Object3D) => {
80
+ controls.objects.delete(object);
81
+ });
82
+ }
83
+
84
+ getRenderStats() {
85
+ return {
86
+ callCount: this.#renderer?.info.render.calls,
87
+ programCount: this.#renderer?.info.programs.length,
88
+ triangleCount: this.#renderer?.info.render.triangles,
89
+ };
90
+ }
91
+
92
+ disconnectedCallback() {
93
+ this.#observer?.disconnect();
94
+ this.#observer = null;
95
+ this.#renderer?.dispose();
96
+ this.#renderer = null;
97
+
98
+ Bus.unsubscribe(this, "add");
99
+ Bus.unsubscribe(this, "remove");
100
+ Bus.unsubscribe(this, "control");
101
+ Bus.unsubscribe(this, "uncontrol");
102
+ }
103
+
104
+ attributeChangedCallback(
105
+ name: string,
106
+ _oldValue: string | null,
107
+ newValue: string | null
108
+ ) {
109
+ switch (name) {
110
+ case "key":
111
+ this.key = newValue;
112
+ break;
113
+ }
114
+ }
115
+
116
+ setViewerKey(viewerKey: string) {
117
+ this.#scene.setViewerKey(viewerKey);
118
+ }
119
+
120
+ async fetchAssets(...args: Parameters<ThreeScene["fetchAssets"]>) {
121
+ if (this.#scene) return this.#scene.fetchAssets(...args);
122
+ }
123
+
124
+ get coreScene() {
125
+ return this.#scene.coreScene;
126
+ }
127
+ }
package/stream.ts ADDED
@@ -0,0 +1,217 @@
1
+ import {
2
+ PerspectiveCamera as ThreeCamera,
3
+ WebGLRenderer as ThreeRenderer,
4
+ } from "three";
5
+ import {
6
+ MirisScene as ThreeScene,
7
+ MirisStream as ThreeStream,
8
+ MirisControls as ThreeControls,
9
+ } from "@miris-inc/three";
10
+ import Bus from "./bus";
11
+ import Primitive from "./primitive";
12
+ import type Scene from "./scene";
13
+
14
+ export default class MirisStream extends Primitive {
15
+ static observedAttributes = ["uuid", "key", "disabled", "controls"];
16
+
17
+ #disabled = false;
18
+ // prettier-ignore
19
+ get disabled() { return this.#disabled }
20
+ set disabled(disabled) {
21
+ if (disabled === this.#disabled) return;
22
+
23
+ this.#disabled = disabled;
24
+
25
+ if (disabled) {
26
+ this.setAttribute("disabled", "");
27
+ this._disable();
28
+ } else {
29
+ this.removeAttribute("disabled");
30
+ this._enable();
31
+ }
32
+ }
33
+
34
+ #uuid: string | null = null;
35
+ // prettier-ignore
36
+ get uuid() { return this.#uuid }
37
+ set uuid(uuid) {
38
+ if (uuid === this.#uuid) return;
39
+
40
+ if (this._threeObject) this._threeObject.clearBoxes();
41
+ const oldUuid = this.#uuid;
42
+
43
+ this.#uuid = uuid;
44
+
45
+ if (uuid) {
46
+ this.setAttribute("uuid", uuid);
47
+ if (oldUuid) this._disable();
48
+ this._enable();
49
+ } else {
50
+ this.removeAttribute("uuid");
51
+ this._disable();
52
+ }
53
+ }
54
+
55
+ #controls = false;
56
+ // prettier-ignore
57
+ get controls() { return this.#controls }
58
+ set controls(controls) {
59
+ if (controls === this.#controls) return;
60
+
61
+ this.#controls = controls;
62
+
63
+ if (controls) this.#control();
64
+ else this.#uncontrol();
65
+ }
66
+
67
+ #threeControls: ThreeControls | null = null;
68
+
69
+ #key: string | null = null;
70
+ // prettier-ignore
71
+ get key() { return this.#key }
72
+ set key(key) {
73
+ if (key === this.#key) return;
74
+
75
+ this.#key = key;
76
+
77
+ if (key) this.setAttribute("key", key);
78
+ else this.removeAttribute("key");
79
+ }
80
+
81
+ protected override _threeObject: ThreeStream | null = null;
82
+
83
+ #renderer: ThreeRenderer | null = null;
84
+ #observer: ResizeObserver | null = null;
85
+ #shadow: ShadowRoot | null = null;
86
+ #scene: ThreeScene | null = null;
87
+
88
+ constructor() {
89
+ super();
90
+
91
+ this.#key = this.getAttribute("key");
92
+ }
93
+
94
+ #enableScene() {
95
+ if (this.#renderer) return;
96
+
97
+ const renderer = new ThreeRenderer({ alpha: true });
98
+ const scene = new ThreeScene(this.key);
99
+ const camera = new ThreeCamera();
100
+ const controls = new ThreeControls(null, camera, renderer.domElement);
101
+ const observer = new ResizeObserver((entries: ResizeObserverEntry[]) => {
102
+ const entry = entries[entries.length - 1]!;
103
+ const width = entry.contentBoxSize[0]!.inlineSize;
104
+ const height = entry.contentBoxSize[0]!.blockSize;
105
+
106
+ renderer.setPixelRatio(devicePixelRatio);
107
+ renderer.setSize(width, height, false);
108
+
109
+ camera.aspect = width / height;
110
+ camera.updateProjectionMatrix();
111
+ });
112
+
113
+ if (!this.#shadow) {
114
+ this.#shadow = this.attachShadow({ mode: "closed" });
115
+ const sheet = new CSSStyleSheet();
116
+ sheet.insertRule(":host { display: block }");
117
+ sheet.insertRule("canvas { display: block; width: 100%; height: 100% }");
118
+ this.#shadow.adoptedStyleSheets.push(sheet);
119
+ this.#shadow.append(renderer.domElement);
120
+ }
121
+
122
+ if (this._threeObject) scene.add(this._threeObject);
123
+
124
+ observer.observe(this);
125
+ renderer.setAnimationLoop(() => renderer.render(scene, camera));
126
+ this.#renderer = renderer;
127
+ this.#scene = scene;
128
+ this.#threeControls = controls;
129
+ this.#observer = observer;
130
+ }
131
+
132
+ override _enable() {
133
+ if (this.disabled) return;
134
+
135
+ if (!this._threeObject && this.uuid) {
136
+ this._threeObject = new ThreeStream(this.uuid, this.key);
137
+ this._threeObject.name = this.uuid;
138
+ }
139
+
140
+ super._enable();
141
+
142
+ if (this.#scene && this._threeObject) this.#scene.add(this._threeObject);
143
+ if (!this._parent && this.uuid) this.#enableScene();
144
+
145
+ if (this.controls) this.#control();
146
+ }
147
+
148
+ #disableScene() {
149
+ if (this.#renderer) {
150
+ this.#renderer?.dispose();
151
+ this.#renderer = null;
152
+ }
153
+
154
+ if (this.#observer) {
155
+ this.#observer?.disconnect();
156
+ this.#observer = null;
157
+ }
158
+ }
159
+
160
+ override _disable() {
161
+ // // Cannot implement until Aqua allows multiple scenes per page
162
+ // if (!this._parent) this.#disableScene();
163
+
164
+ if (this._threeObject) this.#scene?.remove(this._threeObject);
165
+
166
+ if (this.controls) this.#uncontrol();
167
+
168
+ super._disable();
169
+ }
170
+
171
+ #control() {
172
+ if (!this.controls || !this._threeObject) return;
173
+
174
+ const scene = this.closest("miris-scene") as Scene | null;
175
+
176
+ if (scene) Bus.publish(scene, "control", this._threeObject);
177
+ else this.#threeControls?.objects.add(this._threeObject);
178
+ }
179
+
180
+ #uncontrol() {
181
+ if (this.controls || !this._threeObject) return;
182
+
183
+ const scene = this.closest("miris-scene") as Scene | null;
184
+
185
+ if (scene) Bus.publish(scene, "uncontrol", this._threeObject);
186
+ else this.#threeControls?.objects.delete(this._threeObject);
187
+ }
188
+
189
+ attributeChangedCallback(
190
+ name: string,
191
+ _oldValue: string | null,
192
+ newValue: string | null
193
+ ) {
194
+ switch (name) {
195
+ case "controls":
196
+ this.controls = newValue !== null;
197
+ break;
198
+ case "disabled":
199
+ this.disabled = newValue !== null;
200
+ break;
201
+ case "key":
202
+ this.#key = newValue;
203
+ break;
204
+ case "uuid":
205
+ this.uuid = newValue;
206
+ break;
207
+ }
208
+ }
209
+
210
+ update() {
211
+ if (this._threeObject) this._threeObject?.update();
212
+ }
213
+
214
+ toggleRenderBounds(shouldRender: boolean) {
215
+ if (this._threeObject) this._threeObject?.toggleRenderBounds(shouldRender);
216
+ }
217
+ }