Skip to content

Commit 7419b31

Browse files
cabaniermeta-codesync[bot]
authored andcommitted
first pass at adding support for quad and cylinder layers
Summary: This diff adds basic support for quad and cylinder layers and adds an example. Reviewed By: felixtrz Differential Revision: D99745631 fbshipit-source-id: 71c507036d0adb9fb53ed13ce94118e1200fb20f
1 parent a77a6dd commit 7419b31

11 files changed

Lines changed: 969 additions & 0 deletions

File tree

examples/layers/index.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta
6+
name="viewport"
7+
content="width=device-width, initial-scale=1, shrink-to-fit=no"
8+
/>
9+
<title>IWSDK Layers Example</title>
10+
</head>
11+
<body style="margin: 0">
12+
<div id="scene-container"></div>
13+
<script type="module" src="/src/index.ts"></script>
14+
</body>
15+
</html>

examples/layers/package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "layers",
3+
"version": "0.1.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "iwsdk dev up --open --foreground",
8+
"dev:runtime": "vite",
9+
"dev:down": "iwsdk dev down",
10+
"dev:status": "iwsdk dev status",
11+
"build": "vite build",
12+
"preview": "vite preview",
13+
"fresh:install": "npx rimraf package-lock.json node_modules && npm install",
14+
"fresh:ci": "npx rimraf node_modules && npm ci",
15+
"fresh:dev": "npm run fresh:install && npm run dev",
16+
"fresh:build": "npm run fresh:install && npm run build"
17+
},
18+
"dependencies": {
19+
"@iwsdk/core": "file:../../packages/core/iwsdk-core.tgz",
20+
"three": "npm:super-three@0.181.0"
21+
},
22+
"devDependencies": {
23+
"@iwsdk/cli": "file:../../packages/cli/iwsdk-cli.tgz",
24+
"@iwsdk/vite-plugin-dev": "file:../../packages/vite-plugin-dev/iwsdk-vite-plugin-dev.tgz",
25+
"@types/three": "^0.178.1",
26+
"typescript": "^5.5.0",
27+
"vite": "^7.1.4",
28+
"vite-plugin-mkcert": "^1.17.0"
29+
},
30+
"engines": {
31+
"node": ">=20.19.0"
32+
}
33+
}

examples/layers/src/index.ts

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import {
2+
AmbientLight,
3+
BoxGeometry,
4+
DirectionalLight,
5+
GridHelper,
6+
Mesh,
7+
MeshBasicMaterial,
8+
MeshStandardMaterial,
9+
OrthographicCamera,
10+
PlaneGeometry,
11+
Scene,
12+
SessionMode,
13+
Shape,
14+
ShapeGeometry,
15+
SphereGeometry,
16+
TorusGeometry,
17+
World,
18+
XRCylinderLayer,
19+
XRQuadLayer,
20+
} from '@iwsdk/core';
21+
22+
// ---------------------------------------------------------------------------
23+
// Helper: create a star Shape
24+
// ---------------------------------------------------------------------------
25+
function createStarShape(
26+
outerRadius: number,
27+
innerRadius: number,
28+
points: number,
29+
): Shape {
30+
const shape = new Shape();
31+
for (let i = 0; i < points * 2; i++) {
32+
const angle = (i * Math.PI) / points - Math.PI / 2;
33+
const r = i % 2 === 0 ? outerRadius : innerRadius;
34+
const x = Math.cos(angle) * r;
35+
const y = Math.sin(angle) * r;
36+
if (i === 0) {
37+
shape.moveTo(x, y);
38+
} else {
39+
shape.lineTo(x, y);
40+
}
41+
}
42+
shape.closePath();
43+
return shape;
44+
}
45+
46+
// ---------------------------------------------------------------------------
47+
// Helper: create a rounded rectangle Shape
48+
// ---------------------------------------------------------------------------
49+
function createRoundedRectShape(w: number, h: number, r: number): Shape {
50+
const hw = w / 2;
51+
const hh = h / 2;
52+
const shape = new Shape();
53+
shape.moveTo(-hw + r, -hh);
54+
shape.lineTo(hw - r, -hh);
55+
shape.quadraticCurveTo(hw, -hh, hw, -hh + r);
56+
shape.lineTo(hw, hh - r);
57+
shape.quadraticCurveTo(hw, hh, hw - r, hh);
58+
shape.lineTo(-hw + r, hh);
59+
shape.quadraticCurveTo(-hw, hh, -hw, hh - r);
60+
shape.lineTo(-hw, -hh + r);
61+
shape.quadraticCurveTo(-hw, -hh, -hw + r, -hh);
62+
return shape;
63+
}
64+
65+
// ---------------------------------------------------------------------------
66+
// Quad layer content – a star-shaped panel with a rotating cube
67+
// ---------------------------------------------------------------------------
68+
69+
const quadScene = new Scene();
70+
// No background color — transparent outside the star
71+
72+
const quadCamera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
73+
quadCamera.position.set(0, 0, 3);
74+
75+
const quadLight = new AmbientLight(0xffffff, 0.6);
76+
quadScene.add(quadLight);
77+
const quadDirLight = new DirectionalLight(0xffffff, 1);
78+
quadDirLight.position.set(1, 2, 3);
79+
quadScene.add(quadDirLight);
80+
81+
// Star-shaped background
82+
const starShape = createStarShape(0.9, 0.4, 5);
83+
const starBg = new Mesh(
84+
new ShapeGeometry(starShape),
85+
new MeshBasicMaterial({ color: 0x202040 }),
86+
);
87+
starBg.position.z = -0.1; // behind the cube
88+
quadScene.add(starBg);
89+
90+
const cube = new Mesh(
91+
new BoxGeometry(0.4, 0.4, 0.4),
92+
new MeshStandardMaterial({ color: 0x4488ff }),
93+
);
94+
quadScene.add(cube);
95+
96+
// ---------------------------------------------------------------------------
97+
// Cylinder layer content – rounded-corner panel with orbiting shapes
98+
// ---------------------------------------------------------------------------
99+
100+
const cylScene = new Scene();
101+
// No background color — transparent outside the rounded rect
102+
103+
const cylCamera = new OrthographicCamera(-2, 2, 1, -1, 0.1, 10);
104+
cylCamera.position.set(0, 0, 3);
105+
106+
const cylLight = new AmbientLight(0xffffff, 0.6);
107+
cylScene.add(cylLight);
108+
const cylDirLight = new DirectionalLight(0xffffff, 1);
109+
cylDirLight.position.set(-1, 2, 3);
110+
cylScene.add(cylDirLight);
111+
112+
// Rounded rectangle background
113+
const roundedRect = createRoundedRectShape(3.8, 1.8, 0.3);
114+
const roundedBg = new Mesh(
115+
new ShapeGeometry(roundedRect),
116+
new MeshBasicMaterial({ color: 0x402020 }),
117+
);
118+
roundedBg.position.z = -0.1;
119+
cylScene.add(roundedBg);
120+
121+
const torus = new Mesh(
122+
new TorusGeometry(0.3, 0.1, 16, 32),
123+
new MeshStandardMaterial({ color: 0xff4444 }),
124+
);
125+
cylScene.add(torus);
126+
127+
const sphere = new Mesh(
128+
new SphereGeometry(0.2, 16, 16),
129+
new MeshStandardMaterial({ color: 0x44ff44 }),
130+
);
131+
cylScene.add(sphere);
132+
133+
// ---------------------------------------------------------------------------
134+
// Main app
135+
// ---------------------------------------------------------------------------
136+
137+
World.create(document.getElementById('scene-container') as HTMLDivElement, {
138+
xr: {
139+
sessionMode: SessionMode.ImmersiveVR,
140+
features: { layers: true },
141+
},
142+
}).then((world) => {
143+
world.camera.position.set(0, 1.5, 0);
144+
145+
// --- Main scene content (rendered in the projection layer) ---
146+
const grid = new GridHelper(10, 10, 0x888888, 0x444444);
147+
world.scene.add(grid);
148+
149+
const floor = new Mesh(
150+
new PlaneGeometry(10, 10),
151+
new MeshBasicMaterial({ color: 0x333333 }),
152+
);
153+
floor.rotation.x = -Math.PI / 2;
154+
floor.position.y = -0.01;
155+
world.scene.add(floor);
156+
157+
const pillar1 = new Mesh(
158+
new BoxGeometry(0.3, 2, 0.3),
159+
new MeshBasicMaterial({ color: 0xcc8844 }),
160+
);
161+
pillar1.position.set(-2, 1, -3);
162+
world.scene.add(pillar1);
163+
164+
const pillar2 = new Mesh(
165+
new BoxGeometry(0.3, 2, 0.3),
166+
new MeshBasicMaterial({ color: 0xcc8844 }),
167+
);
168+
pillar2.position.set(2, 1, -3);
169+
world.scene.add(pillar2);
170+
171+
const orb = new Mesh(
172+
new SphereGeometry(0.4, 32, 32),
173+
new MeshBasicMaterial({ color: 0xffaa00 }),
174+
);
175+
orb.position.set(0, 0.4, -3);
176+
world.scene.add(orb);
177+
178+
const startTime = performance.now();
179+
180+
// --- Quad layer: star-shaped floating panel ---
181+
const quadEntity = world.createTransformEntity();
182+
quadEntity.object3D!.position.set(-0.8, 1.5, -2);
183+
quadEntity.addComponent(XRQuadLayer, {
184+
width: 1.0,
185+
height: 1.0,
186+
pixelWidth: 1024,
187+
pixelHeight: 1024,
188+
renderCallback: () => {
189+
const elapsed = (performance.now() - startTime) / 1000;
190+
quadEntity.object3D!.position.y = 1.5 + Math.sin(elapsed) * 0.2;
191+
orb.position.x = Math.cos(elapsed * 0.5) * 1;
192+
orb.position.y = 1.5 + Math.sin(elapsed * 0.5) * 1;
193+
cube.rotation.x = elapsed * 0.7;
194+
cube.rotation.y = elapsed;
195+
world.renderer.render(quadScene, quadCamera);
196+
},
197+
});
198+
199+
// --- Cylinder layer: rounded-corner curved panel ---
200+
const cylEntity = world.createTransformEntity();
201+
cylEntity.object3D!.position.set(0.8, 1.5, -2);
202+
cylEntity.addComponent(XRCylinderLayer, {
203+
radius: 2.0,
204+
centralAngle: Math.PI / 3,
205+
aspectRatio: 2.0,
206+
pixelWidth: 1920,
207+
pixelHeight: 960,
208+
renderCallback: () => {
209+
const elapsed = (performance.now() - startTime) / 1000;
210+
torus.position.x = Math.cos(elapsed) * 0.8;
211+
torus.position.y = Math.sin(elapsed * 0.7) * 0.3;
212+
torus.rotation.x = elapsed * 1.2;
213+
sphere.position.x = Math.cos(elapsed + Math.PI) * 0.8;
214+
sphere.position.y = Math.sin(elapsed * 0.5 + 1) * 0.3;
215+
world.renderer.render(cylScene, cylCamera);
216+
},
217+
});
218+
});

examples/layers/tsconfig.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"module": "ESNext",
5+
"moduleResolution": "Bundler",
6+
"strict": true,
7+
"jsx": "preserve",
8+
"resolveJsonModule": true,
9+
"isolatedModules": true,
10+
"noEmit": true,
11+
"skipLibCheck": true,
12+
"types": []
13+
},
14+
"include": ["src/**/*.ts"]
15+
}

examples/layers/vite.config.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { iwsdkDev } from '@iwsdk/vite-plugin-dev';
2+
import { defineConfig } from 'vite';
3+
import mkcert from 'vite-plugin-mkcert';
4+
5+
export default defineConfig({
6+
plugins: [
7+
mkcert(),
8+
iwsdkDev({
9+
emulator: {
10+
device: 'metaQuest3',
11+
activation: 'always',
12+
injectOnBuild: true,
13+
},
14+
ai: { mode: 'agent', screenshotSize: { width: 500, height: 500 } },
15+
verbose: true,
16+
}),
17+
],
18+
server: { host: '0.0.0.0' },
19+
build: {
20+
outDir: 'dist',
21+
sourcemap: process.env.NODE_ENV !== 'production',
22+
target: 'esnext',
23+
rollupOptions: { input: './index.html' },
24+
},
25+
esbuild: { target: 'esnext' },
26+
optimizeDeps: {
27+
esbuildOptions: { target: 'esnext' },
28+
},
29+
publicDir: 'public',
30+
base: './',
31+
});

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export * from './audio/index.js';
2727
export * from './physics/index.js';
2828
export * from './camera/index.js';
2929
export * from './depth/index.js';
30+
export * from './layers/index.js';
3031
export * from './mcp/index.js';
3132

3233
// re-exports

packages/core/src/init/world-initializer.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ import {
3030
Pressed,
3131
} from '../input/index.js';
3232
import { InputSystem } from '../input/index.js';
33+
import {
34+
XRQuadLayer,
35+
XRCylinderLayer,
36+
XRLayerState,
37+
XRLayerSystem,
38+
} from '../layers/index.js';
3339
import { LevelTag, LevelRoot } from '../level/index.js';
3440
import { LevelSystem } from '../level/index.js';
3541
import { LocomotionSystem, TurningMethod } from '../locomotion/index.js';
@@ -476,6 +482,7 @@ function registerFeatureSystems(
476482
const sceneUnderstandingEnabled = !!sceneUnderstanding;
477483
const environmentRaycastEnabled = !!config.features.environmentRaycast;
478484
const cameraEnabled = !!config.features.camera;
485+
479486
const spatialUI = config.features.spatialUI as
480487
| boolean
481488
| {
@@ -559,6 +566,15 @@ function registerFeatureSystems(
559566
world.registerComponent(CameraSource).registerSystem(CameraSystem);
560567
}
561568

569+
// WebXR composition layers (quad/cylinder)
570+
if (config.xr.features?.layers) {
571+
world
572+
.registerComponent(XRQuadLayer)
573+
.registerComponent(XRCylinderLayer)
574+
.registerComponent(XRLayerState)
575+
.registerSystem(XRLayerSystem, { priority: 1 });
576+
}
577+
562578
// Spatial UI systems (Panel, ScreenSpace, Follow)
563579
if (spatialUIEnabled) {
564580
const forwardHtmlEvents =

packages/core/src/layers/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
export * from './xr-quad-layer.js';
9+
export * from './xr-cylinder-layer.js';
10+
export * from './xr-layer-system.js';

0 commit comments

Comments
 (0)