Skip to content

Commit 7610e68

Browse files
committed
feat: add webxr demo
1 parent 85923a7 commit 7610e68

File tree

1 file changed

+278
-0
lines changed

1 file changed

+278
-0
lines changed

examples/webxr.html

+278
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport"
7+
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
8+
<title>Hilo3d XR Demo</title>
9+
<link rel="stylesheet" type="text/css" href="./example.css">
10+
</head>
11+
12+
<body>
13+
<div id="container"></div>
14+
<script src="../build/Hilo3d.js"></script>
15+
<script src="./js/stats.js"></script>
16+
<script src="./js/OrbitControls.js"></script>
17+
<script src="./js/init.js"></script>
18+
<!-- <script src="./js/vconsole.min.js"></script> -->
19+
<script>
20+
renderer.useVao = true;
21+
// renderer.clearColor = new Hilo3d.Color(0, 0, 0, 0);
22+
23+
function rand(min, max) {
24+
return Math.random() * (max - min) + min;
25+
}
26+
27+
function randArr(arr) {
28+
return arr[Math.floor(Math.random() * arr.length)];
29+
}
30+
31+
const container = new Hilo3d.Node();
32+
stage.addChild(container);
33+
var loader = new Hilo3d.BasicLoader();
34+
loader.load({
35+
src: '//gw.alicdn.com/tfs/TB1T1wEizTpK1RjSZKPXXa3UpXa-512-512.png',
36+
crossOrigin: 'anonymous',
37+
useInstanced: true
38+
}).then(function (image) {
39+
return new Hilo3d.Texture({ image: image });
40+
}, function (err) {
41+
return new Hilo3d.Color(1, 0, 0);
42+
}).then(function (diffuse) {
43+
var textureMaterial = new Hilo3d.BasicMaterial({
44+
diffuse: diffuse,
45+
side: Hilo3d.constants.FRONT_AND_BACK
46+
});
47+
var colorMaterial = new Hilo3d.BasicMaterial({
48+
diffuse: new Hilo3d.Color(0.3, 0.6, 0.9),
49+
side: Hilo3d.constants.FRONT_AND_BACK
50+
});
51+
var planeGeometry = new Hilo3d.PlaneGeometry();
52+
var sphereGeometry = new Hilo3d.SphereGeometry({
53+
radius: 0.3
54+
});
55+
var boxGeometry = new Hilo3d.BoxGeometry({
56+
width: 0.3,
57+
height: 0.3,
58+
depth: 0.3
59+
});
60+
boxGeometry.setAllRectUV([[0, 1], [1, 1], [1, 0], [0, 0]]);
61+
62+
var geometryes = [planeGeometry, sphereGeometry, boxGeometry];
63+
var materials = [colorMaterial, textureMaterial];
64+
65+
for (var i = 0; i < 100; i++) {
66+
let r = 1;
67+
var rect = new Hilo3d.Mesh({
68+
frustumTest: true,
69+
geometry: randArr(geometryes),
70+
material: randArr(materials),
71+
x: rand(-r * 2, r * 2),
72+
y: rand(-r * 2, r * 2),
73+
z: rand(-r * 2, r * 2)
74+
});
75+
rect.rotationX = Math.random() * 360;
76+
rect.rotationY = Math.random() * 360;
77+
rect.rotationZ = Math.random() * 360;
78+
rect.setScale(rand(0.2, 0.3));
79+
rect.onUpdate = function () {
80+
if (this !== xrSelectedInfo.mesh) {
81+
this.rotationX += 0.5;
82+
this.rotationY += 0.5;
83+
this.rotationZ += 0.5;
84+
}
85+
};
86+
container.addChild(rect);
87+
}
88+
});
89+
90+
91+
async function initXR() {
92+
const supported = await navigator.xr.isSessionSupported('immersive-ar');
93+
if (supported) {
94+
var enterXrBtn = document.createElement("button");
95+
enterXrBtn.innerHTML = "Enter AR";
96+
enterXrBtn.style.cssText = "position: absolute; bottom: 10px; left: 50%; z-index: 9999;";
97+
enterXrBtn.addEventListener("click", beginXRSession);
98+
document.body.appendChild(enterXrBtn);
99+
} else {
100+
console.log("Session not supported: " + reason);
101+
}
102+
}
103+
104+
let xrSession = null;
105+
let xrReferenceSpace = null;
106+
const xrSelectedInfo = {
107+
started: false,
108+
mesh: null,
109+
meshPos: null,
110+
point: null,
111+
distance: 0,
112+
};
113+
async function beginXRSession() {
114+
xrSession = await navigator.xr.requestSession('immersive-ar');
115+
xrSession.addEventListener('end', () => {
116+
xrSession = null;
117+
camera.updateProjectionMatrix();
118+
camera.position.set(0, 0, 3);
119+
renderer.clearColor.set(0.3, 0.35, 0.35, 1);
120+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
121+
gl.viewport(0, 0, renderer.width, renderer.height);
122+
onXRFrame(0);
123+
});
124+
xrSession.addEventListener('selectstart', (evt) => {
125+
updateHandLine(evt.frame);
126+
const hitInfo = hitTest()?.[0];
127+
if (!hitInfo) {
128+
return;
129+
}
130+
xrSelectedInfo.started = true;
131+
xrSelectedInfo.mesh = hitInfo.mesh;
132+
xrSelectedInfo.meshPos = hitInfo.mesh.worldMatrix.getTranslation();
133+
xrSelectedInfo.point = hitInfo.point;
134+
xrSelectedInfo.distance = hitInfo.distance;
135+
console.log('hit', hitInfo.mesh.id, hitInfo.point.toArray().join(','), xrSelectedInfo);
136+
});
137+
xrSession.addEventListener('selectend', () => {
138+
xrSelectedInfo.started = false;
139+
xrSelectedInfo.mesh = null;
140+
console.log('selectend');
141+
});
142+
143+
xrSession.addEventListener('inputsourceschange', (evt) => {
144+
// drawHandLine(evt.frame);
145+
});
146+
147+
xrReferenceSpace = await xrSession.requestReferenceSpace('local');
148+
await renderer.gl.makeXRCompatible();
149+
150+
const xrWebGLBaseLayer = new XRWebGLLayer(xrSession, renderer.gl);
151+
152+
xrSession.updateRenderState({
153+
baseLayer: xrWebGLBaseLayer,
154+
});
155+
xrSession.requestAnimationFrame(onXRFrame);
156+
}
157+
158+
const line = new Hilo3d.Mesh({
159+
geometry: new Hilo3d.Geometry({
160+
mode: Hilo3d.constants.LINES,
161+
vertices: new Hilo3d.GeometryData(new Float32Array([
162+
0, 0, 0,
163+
0, 0, -10
164+
]), 3),
165+
colors: new Hilo3d.GeometryData(new Float32Array([
166+
1, 0, 0, 1,
167+
1, 0, 0, 1
168+
]), 4),
169+
}),
170+
material: new Hilo3d.BasicMaterial({
171+
lightType: 'NONE',
172+
side: Hilo3d.constants.FRONT_AND_BACK
173+
})
174+
});
175+
const lineContainer = new Hilo3d.Node();
176+
lineContainer.addChild(line);
177+
stage.addChild(lineContainer);
178+
179+
function updateHandLine(frame) {
180+
const inputSource = frame.session.inputSources[0];
181+
let targetRayPose = frame.getPose(inputSource.targetRaySpace, xrReferenceSpace);
182+
if (!targetRayPose) {
183+
return;
184+
}
185+
// console.log('inputSource', targetRayPose.transform.matrix.join(','));
186+
lineContainer.matrix.fromArray(targetRayPose.transform.matrix);
187+
lineContainer.matrix.mul(stage.matrix.clone().invert(), lineContainer.matrix);
188+
189+
if (xrSelectedInfo.started) {
190+
const newPoint = new Hilo3d.Vector3(0, 0, -xrSelectedInfo.distance);
191+
newPoint.transformMat4(lineContainer.worldMatrix);
192+
newPoint.sub(xrSelectedInfo.point);
193+
const newWorldPos = xrSelectedInfo.meshPos.clone().add(newPoint);
194+
newWorldPos.transformMat4(xrSelectedInfo.mesh.parent.worldMatrix.clone().invert());
195+
xrSelectedInfo.mesh.position.copy(newWorldPos);
196+
line.setScale(1, 1, xrSelectedInfo.distance / 10);
197+
} else {
198+
hitTest();
199+
}
200+
}
201+
202+
// alert(1);
203+
const ray = new Hilo3d.Ray();
204+
function hitTest() {
205+
ray.origin.set(0, 0, 0);
206+
ray.direction.set(0, 0, -1);
207+
lineContainer.updateMatrixWorld();
208+
ray.transformMat4(lineContainer.worldMatrix);
209+
const hitTestStartTime = Date.now();
210+
var hitResult = container.raycast(ray);
211+
hitResult?.forEach(hitInfo => {
212+
hitInfo.distance = ray.distance(hitInfo.point);
213+
});
214+
hitResult?.sort((a, b) => a.distance - b.distance);
215+
// console.log('hitTest', Date.now() - hitTestStartTime);
216+
if (!xrSelectedInfo.started) {
217+
if (hitResult && hitResult.length) {
218+
line.setScale(1, 1, hitResult[0].distance / 10);
219+
} else {
220+
line.setScale(1, 1, 1);
221+
}
222+
}
223+
return hitResult;
224+
}
225+
226+
let lastTS = 0;
227+
function onXRFrame(ts, xrFrame) {
228+
const gl = renderer.gl;
229+
if (xrSession && xrFrame) {
230+
renderer.clearColor.set(0, 0, 0, 0);
231+
updateHandLine(xrFrame);
232+
let glLayer = xrSession.renderState.baseLayer;
233+
gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer);
234+
let pose = xrFrame.getViewerPose(xrReferenceSpace);
235+
if (pose) {
236+
for (let i = 0; i < 2; i++) {
237+
const view = pose.views[i];
238+
if (!view) {
239+
continue;
240+
}
241+
let viewport = glLayer.getViewport(view);
242+
// console.log('viewport', [viewport.x, viewport.y, viewport.width, viewport.height].join(','));
243+
gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
244+
245+
camera.matrix.fromArray(view.transform.matrix);
246+
camera.projectionMatrix.fromArray(view.projectionMatrix);
247+
camera.updateMatrixWorld();
248+
camera.updateViewProjectionMatrix();
249+
250+
if (i === 0) {
251+
stage.tick(ts - lastTS);
252+
} else {
253+
renderer.renderScene();
254+
}
255+
}
256+
}
257+
} else if (gl) {
258+
stage.tick(ts - lastTS);
259+
}
260+
261+
lastTS = ts;
262+
263+
if (xrSession && xrFrame) {
264+
xrSession.requestAnimationFrame(onXRFrame);
265+
} else {
266+
requestAnimationFrame(onXRFrame);
267+
}
268+
}
269+
270+
ticker.removeTick(stage);
271+
renderer.initContext();
272+
requestAnimationFrame(onXRFrame);
273+
274+
initXR();
275+
</script>
276+
</body>
277+
278+
</html>

0 commit comments

Comments
 (0)