Skip to content

Commit f637b94

Browse files
authored
Refactor XR session handling into a script (#184)
* Refactor XR session handling into a script * Lint fixes * Tweak xr-session script
1 parent 781d507 commit f637b94

18 files changed

+209
-102
lines changed

examples/animation.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<pc-asset src="../node_modules/playcanvas/scripts/esm/xr-controllers.mjs"></pc-asset>
2222
<pc-asset src="../node_modules/playcanvas/scripts/esm/xr-navigation.mjs"></pc-asset>
2323
<pc-asset src="../node_modules/playcanvas/scripts/esm/shadow-catcher.mjs"></pc-asset>
24+
<pc-asset src="assets/scripts/xr-session.mjs"></pc-asset>
2425
<pc-asset src="assets/skies/dry-lake-bed-2k.hdr" id="lake-bed"></pc-asset>
2526
<pc-asset src="assets/models/t-rex.glb" id="t-rex"></pc-asset>
2627
<!-- Scene -->
@@ -43,6 +44,7 @@
4344
<pc-scripts>
4445
<pc-script name="xrControllers"></pc-script>
4546
<pc-script name="xrNavigation"></pc-script>
47+
<pc-script name="xrSession"></pc-script>
4648
</pc-scripts>
4749
</pc-entity>
4850
<!-- Light -->

examples/annotations.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<pc-asset src="../node_modules/playcanvas/scripts/esm/xr-navigation.mjs"></pc-asset>
2626
<pc-asset src="../node_modules/playcanvas/scripts/esm/shadow-catcher.mjs"></pc-asset>
2727
<pc-asset src="assets/scripts/annotation.mjs"></pc-asset>
28+
<pc-asset src="assets/scripts/xr-session.mjs"></pc-asset>
2829
<pc-asset src="assets/skies/shanghai-riverside-4k.hdr" id="shanghai"></pc-asset>
2930
<pc-asset src="assets/models/jet-fighter.glb" id="jet-fighter"></pc-asset>
3031
<!-- Scene -->
@@ -47,6 +48,7 @@
4748
<pc-scripts>
4849
<pc-script name="xrControllers"></pc-script>
4950
<pc-script name="xrNavigation"></pc-script>
51+
<pc-script name="xrSession"></pc-script>
5052
</pc-scripts>
5153
</pc-entity>
5254
<!-- Light -->
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { Color, Quat, Script, Vec3 } from 'playcanvas';
2+
3+
export class XrSession extends Script {
4+
static scriptName = 'xrSession';
5+
6+
/**
7+
* Event name to start the XR session. Handler expects two arguments:
8+
* (type: 'immersive-ar' | 'immersive-vr', space?: 'bounded-floor' | 'local' | 'local-floor' | 'unbounded' | 'viewer')
9+
* If type is omitted, defaults to 'immersive-vr'. If space is omitted, defaults to 'local-floor'.
10+
* @type {string}
11+
* @attribute
12+
*/
13+
startEvent = 'xr:start';
14+
15+
/**
16+
* Event name to end the XR session.
17+
* @type {string}
18+
* @attribute
19+
*/
20+
endEvent = 'xr:end';
21+
22+
cameraEntity = null;
23+
24+
cameraRootEntity = null;
25+
26+
clearColor = new Color();
27+
28+
originalSkyType = null;
29+
30+
positionRoot = new Vec3();
31+
32+
rotationRoot = new Quat();
33+
34+
positionCamera = new Vec3();
35+
36+
rotationCamera = new Quat();
37+
38+
onKeyDownHandler = null;
39+
40+
initialize() {
41+
this.cameraEntity = this.entity.findComponent('camera')?.entity || null;
42+
this.cameraRootEntity = this.entity || null;
43+
44+
// Listen to global XR lifecycle to mirror example.mjs behavior
45+
this.app.xr?.on('start', this.onXrStart, this);
46+
this.app.xr?.on('end', this.onXrEnd, this);
47+
48+
// Listen for external events to control session
49+
this.app.on(this.startEvent, this.onStartEvent, this);
50+
this.app.on(this.endEvent, this.onEndEvent, this);
51+
52+
// ESC to exit
53+
this.onKeyDownHandler = (event) => {
54+
if (event.key === 'Escape' && this.app.xr?.active) {
55+
this.endSession();
56+
}
57+
};
58+
window.addEventListener('keydown', this.onKeyDownHandler);
59+
}
60+
61+
destroy() {
62+
this.app.xr?.off('start', this.onXrStart, this);
63+
this.app.xr?.off('end', this.onXrEnd, this);
64+
this.app.off(this.startEvent, this.onStartEvent, this);
65+
this.app.off(this.endEvent, this.onEndEvent, this);
66+
if (this.onKeyDownHandler) {
67+
window.removeEventListener('keydown', this.onKeyDownHandler);
68+
this.onKeyDownHandler = null;
69+
}
70+
}
71+
72+
onStartEvent(type = 'immersive-vr', space = 'local-floor') {
73+
this.startSession(type, space);
74+
}
75+
76+
onEndEvent() {
77+
this.endSession();
78+
}
79+
80+
startSession(type = 'immersive-vr', space = 'local-floor') {
81+
if (!this.cameraEntity.camera) {
82+
console.error('XrSession: No cameraEntity.camera found on the entity.');
83+
return;
84+
}
85+
86+
// Start XR on the camera component
87+
this.cameraEntity.camera.startXr(type, space, {
88+
callback: (err) => {
89+
if (err) console.error(`WebXR ${type} failed to start: ${err.message}`);
90+
}
91+
});
92+
}
93+
94+
endSession() {
95+
if (!this.cameraEntity.camera) return;
96+
this.cameraEntity.camera.endXr();
97+
}
98+
99+
onXrStart() {
100+
if (!this.cameraEntity || !this.cameraRootEntity) return;
101+
102+
// Cache original camera rig transforms
103+
this.positionRoot.copy(this.cameraRootEntity.getPosition());
104+
this.rotationRoot.copy(this.cameraRootEntity.getRotation());
105+
this.positionCamera.copy(this.cameraEntity.getPosition());
106+
this.rotationCamera.copy(this.cameraEntity.getRotation());
107+
108+
// Place root at camera position/orientation
109+
this.cameraRootEntity.setPosition(this.positionCamera.x, 0, this.positionCamera.z);
110+
this.cameraRootEntity.setRotation(this.rotationCamera);
111+
112+
if (this.app.xr.type === 'immersive-ar') {
113+
// Make camera background transparent and hide the sky
114+
this.clearColor.copy(this.cameraEntity.camera.clearColor);
115+
this.cameraEntity.camera.clearColor = new Color(0, 0, 0, 0);
116+
this.disableSky();
117+
}
118+
}
119+
120+
onXrEnd() {
121+
if (!this.cameraEntity || !this.cameraRootEntity) return;
122+
123+
// Restore original transforms
124+
this.cameraRootEntity.setPosition(this.positionRoot);
125+
this.cameraRootEntity.setRotation(this.rotationRoot);
126+
this.cameraEntity.setPosition(this.positionCamera);
127+
this.cameraEntity.setRotation(this.rotationCamera);
128+
129+
if (this.app.xr.type === 'immersive-ar') {
130+
this.cameraEntity.camera.clearColor = this.clearColor;
131+
this.restoreSky();
132+
}
133+
}
134+
135+
disableSky() {
136+
const sky = document.querySelector('pc-sky');
137+
if (!sky) return;
138+
if (this.originalSkyType === null) {
139+
this.originalSkyType = sky.getAttribute('type');
140+
}
141+
sky.setAttribute('type', 'none');
142+
}
143+
144+
restoreSky() {
145+
const sky = document.querySelector('pc-sky');
146+
if (!sky) return;
147+
if (this.originalSkyType !== null) {
148+
if (this.originalSkyType) {
149+
sky.setAttribute('type', this.originalSkyType);
150+
} else {
151+
sky.removeAttribute('type');
152+
}
153+
this.originalSkyType = null;
154+
}
155+
}
156+
}

examples/basic-shapes.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<pc-asset id="camera-controls" src="../node_modules/playcanvas/scripts/esm/camera-controls.mjs" preload></pc-asset>
2121
<pc-asset id="xr-controllers" src="../node_modules/playcanvas/scripts/esm/xr-controllers.mjs" preload></pc-asset>
2222
<pc-asset id="xr-navigation" src="../node_modules/playcanvas/scripts/esm/xr-navigation.mjs" preload></pc-asset>
23+
<pc-asset src="assets/scripts/xr-session.mjs"></pc-asset>
2324
<!-- Materials -->
2425
<pc-material id="crimson" diffuse="crimson"></pc-material>
2526
<pc-material id="mediumseagreen" diffuse="mediumseagreen"></pc-material>
@@ -47,6 +48,7 @@
4748
<pc-scripts>
4849
<pc-script name="xrControllers"></pc-script>
4950
<pc-script name="xrNavigation"></pc-script>
51+
<pc-script name="xrSession"></pc-script>
5052
</pc-scripts>
5153
</pc-entity>
5254
<!-- Key Light (Spot) -->

examples/car-configurator.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<pc-asset src="../node_modules/playcanvas/scripts/esm/xr-controllers.mjs"></pc-asset>
2525
<pc-asset src="../node_modules/playcanvas/scripts/esm/xr-navigation.mjs"></pc-asset>
2626
<pc-asset src="assets/scripts/choose-color.mjs"></pc-asset>
27+
<pc-asset src="assets/scripts/xr-session.mjs"></pc-asset>
2728
<pc-asset src="assets/skies/octagon-lamps-photo-studio-2k.hdr" id="studio"></pc-asset>
2829
<pc-asset src="assets/models/porsche-911-carrera-4s.glb" id="car"></pc-asset>
2930
<!-- Scene -->
@@ -48,6 +49,7 @@
4849
<pc-scripts>
4950
<pc-script name="xrControllers"></pc-script>
5051
<pc-script name="xrNavigation"></pc-script>
52+
<pc-script name="xrSession"></pc-script>
5153
</pc-scripts>
5254
</pc-entity>
5355
<!-- Car -->

examples/css/example.css

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ canvas {
3434
display: flex;
3535
position: relative;
3636
height: 40px;
37-
background: rgba(255, 255, 255, 0.9);
37+
background-color: rgba(255, 255, 255, 0.9);
3838
border: 1px solid #ddd;
3939
border-radius: 8px;
4040
cursor: pointer;
@@ -52,8 +52,17 @@ canvas {
5252
.example-button.icon {
5353
width: 40px;
5454
padding: 0;
55+
background-position: center center;
56+
background-repeat: no-repeat;
57+
background-size: 24px 24px;
5558
}
5659

60+
.example-button.icon.icon-ar { background-image: url("../img/ar.svg"); }
61+
.example-button.icon.icon-vr { background-image: url("../img/vr.svg"); }
62+
.example-button.icon.icon-fs-enter { background-image: url("../img/fullscreen-enter.svg"); }
63+
.example-button.icon.icon-fs-exit { background-image: url("../img/fullscreen-exit.svg"); }
64+
.example-button.icon.icon-source { background-image: url("../img/code.svg"); }
65+
5766
.example-button:hover {
58-
background: rgba(255, 255, 255, 1);
67+
background-color: rgba(255, 255, 255, 1);
5968
}

examples/glb.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<pc-asset src="../node_modules/playcanvas/scripts/esm/xr-controllers.mjs"></pc-asset>
2222
<pc-asset src="../node_modules/playcanvas/scripts/esm/xr-navigation.mjs"></pc-asset>
2323
<pc-asset src="assets/scripts/rotate.mjs"></pc-asset>
24+
<pc-asset src="assets/scripts/xr-session.mjs"></pc-asset>
2425
<pc-asset src="assets/skies/octagon-lamps-photo-studio-2k.hdr" id="studio"></pc-asset>
2526
<pc-asset src="assets/models/playcanvas-cube.glb" id="cube"></pc-asset>
2627
<!-- Scene -->
@@ -43,6 +44,7 @@
4344
<pc-scripts>
4445
<pc-script name="xrControllers"></pc-script>
4546
<pc-script name="xrNavigation"></pc-script>
47+
<pc-script name="xrSession"></pc-script>
4648
</pc-scripts>
4749
</pc-entity>
4850
<!-- Light -->

examples/img/ar.svg

Lines changed: 1 addition & 0 deletions
Loading

examples/img/code.svg

Lines changed: 1 addition & 0 deletions
Loading

examples/img/fullscreen-enter.svg

Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)