Skip to content

Commit 2040254

Browse files
authored
Merge branch 'main' into sim-hand-convention-fix
2 parents f9fb1ac + b52c72c commit 2040254

4 files changed

Lines changed: 143 additions & 1 deletion

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
title: VRM Avatar Point-to-Walk
3+
hide_title: true
4+
breadcrumbs: false
5+
pagination_next: null
6+
pagination_prev: null
7+
---
8+
9+
import {SamplesIFrame} from './SamplesIFrame';
10+
11+
<SamplesIFrame
12+
demo="vrm-avatar"
13+
link="https://github.com/google/xrblocks/tree/main/demos/vrm-avatar/"
14+
></SamplesIFrame>

docs/sidebars.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ const sidebars: SidebarsConfig = {
8888
'samples/AI-Simulator',
8989
'samples/Virtual-Screens',
9090
'samples/3DGS-Walkthrough',
91+
'samples/VRM-Avatar',
9192
],
9293
},
9394
],

samples/glasses_ui/main.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {SystemUI} from 'xrblocks/addons/glasses/ui/SystemUI.js';
77
import {CardStack} from 'xrblocks/addons/glasses/ui/CardStack.js';
88
import {CardManager} from 'xrblocks/addons/glasses/ui/CardManager.js';
99
import {GlassesRenderer} from 'xrblocks/addons/glasses/ui/GlassesRenderer.js';
10+
import {GlassesModelManager} from 'xrblocks/addons/glasses/ui/GlassesModelManager.js';
1011

1112
// prettier-ignore
1213
const EMOJIS = ['🚀', '😂', '🔥', '💡', '🎉', '🤖', '✨', '🦖', '🍕', '🌍', '🍔', '👾', '🎨', '🎸', '🎲', '🥑'];
@@ -15,7 +16,7 @@ class GlassesUISample extends xb.Script {
1516
private runningInXr = false;
1617
private systemUiGroup = new THREE.Group();
1718
private systemUi!: SystemUI;
18-
19+
private glassesModelManager = new GlassesModelManager();
1920
private glassesRenderer?: GlassesRenderer;
2021

2122
// Card Display.
@@ -37,6 +38,7 @@ class GlassesUISample extends xb.Script {
3738
})
3839
);
3940

41+
this.add(this.glassesModelManager);
4042
this.add(this.cardManager);
4143

4244
this.createNewCard();
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import * as THREE from 'three';
2+
import * as xb from 'xrblocks';
3+
import type {GLTF} from 'three/addons/loaders/GLTFLoader.js';
4+
5+
const GLASSES_MODEL_FILE =
6+
'https://cdn.jsdelivr.net/gh/xrblocks/proprietary-assets@main/glasses/glasses_without_lens.glb';
7+
8+
function disposeMaterial(material: THREE.Material): void {
9+
for (const key in material) {
10+
const value = material[key as keyof THREE.Material];
11+
if (value instanceof THREE.Texture) {
12+
value.dispose();
13+
}
14+
}
15+
material.dispose();
16+
}
17+
18+
export class GlassesModelManager extends xb.Script {
19+
private _modelUrl = GLASSES_MODEL_FILE;
20+
21+
// Default/Simulator Transform Properties
22+
defaultPosition = new THREE.Vector3(-0.03, 0.001, -0.005);
23+
defaultRotation = new THREE.Euler(0, Math.PI, 0);
24+
defaultScale = new THREE.Vector3(0.05, 0.05, 0.05);
25+
26+
// WebXR Headset Transform Properties
27+
xrPosition = new THREE.Vector3(0.0, 0.0, -0.05);
28+
xrRotation = new THREE.Euler(0, Math.PI, 0);
29+
xrScale = new THREE.Vector3(0.05, 0.05, 0.05);
30+
31+
private glassesModel?: GLTF;
32+
private runningInXrHeadset = false;
33+
34+
get modelUrl() {
35+
return this._modelUrl;
36+
}
37+
38+
set modelUrl(url: string) {
39+
if (this._modelUrl !== url) {
40+
this._modelUrl = url;
41+
if (this.glassesModel) {
42+
this.removeAndDisposeModel(this.glassesModel);
43+
this.glassesModel = undefined;
44+
this.loadGlassesModel();
45+
}
46+
}
47+
}
48+
49+
updateTransform() {
50+
this.positionGlassesModel();
51+
}
52+
53+
async init() {
54+
await this.loadGlassesModel();
55+
}
56+
57+
protected async loadGlassesModel() {
58+
const model = await new xb.ModelLoader().loadGLTF({
59+
url: this._modelUrl,
60+
renderer: xb.core.renderer,
61+
});
62+
this.glassesModel = model;
63+
this.positionGlassesModel();
64+
xb.core.camera.add(model.scene);
65+
xb.add(xb.core.camera);
66+
}
67+
68+
protected positionGlassesModel() {
69+
const glassesModel = this.glassesModel;
70+
if (!glassesModel) return;
71+
if (this.runningInXrHeadset) {
72+
glassesModel.scene.position.copy(this.xrPosition);
73+
glassesModel.scene.rotation.copy(this.xrRotation);
74+
glassesModel.scene.scale.copy(this.xrScale);
75+
} else {
76+
glassesModel.scene.position.copy(this.defaultPosition);
77+
glassesModel.scene.rotation.copy(this.defaultRotation);
78+
glassesModel.scene.scale.copy(this.defaultScale);
79+
}
80+
}
81+
82+
protected setXrHeadset(enabled: boolean) {
83+
if (this.runningInXrHeadset != enabled) {
84+
this.runningInXrHeadset = enabled;
85+
this.positionGlassesModel();
86+
}
87+
}
88+
89+
override update() {
90+
const xrCameras = xb.core.renderer.xr.getCamera()
91+
.cameras as THREE.WebXRCamera[];
92+
this.setXrHeadset(xrCameras.length === 2);
93+
}
94+
95+
override dispose() {
96+
if (this.glassesModel) {
97+
this.removeAndDisposeModel(this.glassesModel);
98+
}
99+
}
100+
101+
private removeAndDisposeModel(model: GLTF) {
102+
model.scene.removeFromParent();
103+
model.scene.traverse((child) => {
104+
if (child instanceof THREE.Mesh) {
105+
if (child.geometry) {
106+
child.geometry.dispose();
107+
}
108+
109+
if (child.material) {
110+
if (Array.isArray(child.material)) {
111+
child.material.forEach((mat: THREE.Material) =>
112+
disposeMaterial(mat)
113+
);
114+
} else {
115+
disposeMaterial(child.material as THREE.Material);
116+
}
117+
}
118+
}
119+
});
120+
}
121+
122+
get model() {
123+
return this.glassesModel;
124+
}
125+
}

0 commit comments

Comments
 (0)