sceneview-mcp 3.2.0 → 3.3.0

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/dist/samples.js CHANGED
@@ -2,10 +2,10 @@ export const SAMPLES = {
2
2
  "model-viewer": {
3
3
  id: "model-viewer",
4
4
  title: "3D Model Viewer",
5
- description: "Full-screen 3D scene with a GLB model, HDR environment, and orbit camera",
6
- tags: ["3d", "model", "environment", "camera"],
7
- dependency: "io.github.sceneview:sceneview:3.1.1",
8
- prompt: "Create an Android Compose screen called `ModelViewerScreen` that loads a GLB file from assets/models/my_model.glb and displays it in a full-screen 3D scene with an orbit camera (drag to rotate, pinch to zoom). Add an HDR environment from assets/environments/sky_2k.hdr for realistic lighting. Use SceneView `io.github.sceneview:sceneview:3.1.1`.",
5
+ description: "Full-screen 3D scene with a GLB model, HDR environment, orbit camera, and animation controls",
6
+ tags: ["3d", "model", "environment", "camera", "animation"],
7
+ dependency: "io.github.sceneview:sceneview:3.3.0",
8
+ prompt: "Create an Android Compose screen that loads a GLB model and displays it with HDR lighting, orbit camera, and animation playback. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
9
9
  code: `@Composable
10
10
  fun ModelViewerScreen() {
11
11
  val engine = rememberEngine()
@@ -17,12 +17,12 @@ fun ModelViewerScreen() {
17
17
  engine = engine,
18
18
  modelLoader = modelLoader,
19
19
  environment = rememberEnvironment(environmentLoader) {
20
- environmentLoader.createHDREnvironment("environments/sky_2k.hdr")!!
20
+ environmentLoader.createHDREnvironment("environments/sky_2k.hdr") ?: createEnvironment(environmentLoader)
21
21
  },
22
22
  mainLightNode = rememberMainLightNode(engine) { intensity = 100_000f },
23
23
  cameraManipulator = rememberCameraManipulator()
24
24
  ) {
25
- rememberModelInstance(modelLoader, "models/my_model.glb")?.let { instance ->
25
+ rememberModelInstance(modelLoader, "models/damaged_helmet.glb")?.let { instance ->
26
26
  ModelNode(
27
27
  modelInstance = instance,
28
28
  scaleToUnits = 1.0f,
@@ -33,75 +33,19 @@ fun ModelViewerScreen() {
33
33
  }
34
34
  }`,
35
35
  },
36
- "geometry-scene": {
37
- id: "geometry-scene",
38
- title: "3D Geometry Scene",
39
- description: "Procedural 3D scene using primitive geometry nodes (cube, sphere, plane) no GLB required",
40
- tags: ["3d", "geometry", "animation"],
41
- dependency: "io.github.sceneview:sceneview:3.1.1",
42
- prompt: "Create an Android Compose screen called `GeometrySceneScreen` that renders a full-screen 3D scene with a red rotating cube, a metallic blue sphere, and a green floor plane. No model files — use SceneView built-in geometry nodes. Orbit camera. Use SceneView `io.github.sceneview:sceneview:3.1.1`.",
43
- code: `@Composable
44
- fun GeometrySceneScreen() {
45
- val engine = rememberEngine()
46
- val materialLoader = rememberMaterialLoader(engine)
47
- val t = rememberInfiniteTransition(label = "spin")
48
- val angle by t.animateFloat(
49
- initialValue = 0f, targetValue = 360f,
50
- animationSpec = infiniteRepeatable(tween(4_000, easing = LinearEasing)),
51
- label = "angle"
52
- )
53
-
54
- Scene(
55
- modifier = Modifier.fillMaxSize(),
56
- engine = engine,
57
- materialLoader = materialLoader,
58
- mainLightNode = rememberMainLightNode(engine) { intensity(80_000f) },
59
- cameraManipulator = rememberCameraManipulator()
60
- ) {
61
- // Rotating red cube
62
- CubeNode(
63
- engine,
64
- size = Size(0.5f, 0.5f, 0.5f),
65
- materialInstance = materialLoader.createColorInstance(
66
- Color.Red, metallic = 0f, roughness = 0.5f
67
- ),
68
- position = Position(x = -0.6f),
69
- rotation = Rotation(y = angle)
70
- )
71
- // Metallic blue sphere
72
- SphereNode(
73
- engine,
74
- radius = 0.3f,
75
- materialInstance = materialLoader.createColorInstance(
76
- Color.Blue, metallic = 0.8f, roughness = 0.2f
77
- ),
78
- position = Position(x = 0.6f)
79
- )
80
- // Floor plane
81
- PlaneNode(
82
- engine,
83
- size = Size(2f, 0f, 2f),
84
- materialInstance = materialLoader.createColorInstance(
85
- Color(0xFF4CAF50), metallic = 0f, roughness = 0.9f
86
- ),
87
- position = Position(y = -0.35f)
88
- )
89
- }
90
- }`,
91
- },
92
- "ar-tap-to-place": {
93
- id: "ar-tap-to-place",
94
- title: "AR Tap-to-Place",
95
- description: "AR scene where each tap places a GLB model on a detected surface. Placed models are pinch-to-scale and drag-to-rotate.",
36
+ "ar-model-viewer": {
37
+ id: "ar-model-viewer",
38
+ title: "AR Tap-to-Place Model Viewer",
39
+ description: "AR scene with plane detection. Tap a surface to place a 3D model with pinch-to-scale and drag-to-rotate gestures.",
96
40
  tags: ["ar", "model", "anchor", "plane-detection", "placement", "gestures"],
97
- dependency: "io.github.sceneview:arsceneview:3.1.1",
98
- prompt: "Create an Android Compose screen called `TapToPlaceScreen` that opens the camera in AR mode. Show a plane detection grid. When the user taps a detected surface, place a 3D GLB model from assets/models/chair.glb at that point. The user should be able to pinch-to-scale and drag-to-rotate after placing. Multiple taps = multiple objects. Use SceneView `io.github.sceneview:arsceneview:3.1.1`.",
41
+ dependency: "io.github.sceneview:arsceneview:3.3.0",
42
+ prompt: "Create an AR screen that detects surfaces and lets the user tap to place a GLB model. Support pinch-to-scale and drag-to-rotate. Use SceneView `io.github.sceneview:arsceneview:3.3.0`.",
99
43
  code: `@Composable
100
- fun TapToPlaceScreen() {
44
+ fun ARModelViewerScreen() {
101
45
  val engine = rememberEngine()
102
46
  val modelLoader = rememberModelLoader(engine)
103
- val modelInstance = rememberModelInstance(modelLoader, "models/chair.glb")
104
- var placedAnchors by remember { mutableStateOf(listOf<Anchor>()) }
47
+ val modelInstance = rememberModelInstance(modelLoader, "models/damaged_helmet.glb")
48
+ var anchor by remember { mutableStateOf<Anchor?>(null) }
105
49
 
106
50
  ARScene(
107
51
  modifier = Modifier.fillMaxSize(),
@@ -112,150 +56,832 @@ fun TapToPlaceScreen() {
112
56
  config.depthMode =
113
57
  if (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC))
114
58
  Config.DepthMode.AUTOMATIC else Config.DepthMode.DISABLED
115
- config.instantPlacementMode = Config.InstantPlacementMode.LOCAL_Y_UP
116
59
  config.lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR
117
60
  },
118
61
  onTouchEvent = { event, hitResult ->
119
62
  if (event.action == MotionEvent.ACTION_UP && hitResult != null)
120
- placedAnchors = placedAnchors + hitResult.createAnchor()
63
+ anchor = hitResult.createAnchor()
121
64
  true
122
65
  }
123
66
  ) {
124
- placedAnchors.forEach { anchor ->
125
- AnchorNode(anchor = anchor) {
126
- ModelNode(
127
- modelInstance = modelInstance ?: return@AnchorNode,
128
- scaleToUnits = 0.5f,
129
- isEditable = true
130
- )
67
+ anchor?.let { a ->
68
+ AnchorNode(anchor = a) {
69
+ modelInstance?.let { instance ->
70
+ ModelNode(
71
+ modelInstance = instance,
72
+ scaleToUnits = 0.5f,
73
+ isEditable = true
74
+ )
75
+ }
131
76
  }
132
77
  }
133
78
  }
134
79
  }`,
135
80
  },
136
- "ar-placement-cursor": {
137
- id: "ar-placement-cursor",
138
- title: "AR Placement Cursor",
139
- description: "AR scene with a reticle that follows the surface at screen center. Tap to confirm placement.",
140
- tags: ["ar", "model", "anchor", "plane-detection", "placement", "camera"],
141
- dependency: "io.github.sceneview:arsceneview:3.1.1",
142
- prompt: "Create an Android Compose AR screen called `ARCursorScreen`. Show a small reticle that snaps to the nearest detected surface at the center of the screen as the user moves the camera. When the user taps, place a GLB model from assets/models/object.glb at that position and hide the reticle. Use SceneView `io.github.sceneview:arsceneview:3.1.1`.",
81
+ "ar-augmented-image": {
82
+ id: "ar-augmented-image",
83
+ title: "AR Augmented Image",
84
+ description: "Detects reference images in the camera feed and overlays 3D models or video above them.",
85
+ tags: ["ar", "model", "image-tracking"],
86
+ dependency: "io.github.sceneview:arsceneview:3.3.0",
87
+ prompt: "Create an AR screen that detects a printed reference image and places a 3D model above it. Use SceneView `io.github.sceneview:arsceneview:3.3.0`.",
143
88
  code: `@Composable
144
- fun ARCursorScreen() {
89
+ fun AugmentedImageScreen() {
145
90
  val engine = rememberEngine()
146
91
  val modelLoader = rememberModelLoader(engine)
147
- val modelInstance = rememberModelInstance(modelLoader, "models/object.glb")
148
- var anchor by remember { mutableStateOf<Anchor?>(null) }
149
- val view = LocalView.current
92
+ val modelInstance = rememberModelInstance(modelLoader, "models/rabbit.glb")
93
+ var augmentedImages by remember { mutableStateOf<Map<String, AugmentedImage>>(emptyMap()) }
150
94
 
151
95
  ARScene(
152
96
  modifier = Modifier.fillMaxSize(),
153
97
  engine = engine,
154
98
  modelLoader = modelLoader,
155
- planeRenderer = true,
156
- sessionConfiguration = { _, config ->
157
- config.instantPlacementMode = Config.InstantPlacementMode.LOCAL_Y_UP
158
- config.lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR
99
+ sessionConfiguration = { session, config ->
100
+ config.addAugmentedImage(
101
+ session, "rabbit",
102
+ assets.open("augmentedimages/rabbit.jpg").use(BitmapFactory::decodeStream)
103
+ )
159
104
  },
160
- onTouchEvent = { event, hitResult ->
161
- if (event.action == MotionEvent.ACTION_UP && hitResult != null)
162
- anchor = hitResult.createAnchor()
163
- true
105
+ onSessionUpdated = { _, frame ->
106
+ frame.getUpdatedAugmentedImages().forEach { image ->
107
+ augmentedImages = augmentedImages.toMutableMap().apply {
108
+ this[image.name] = image
109
+ }
110
+ }
164
111
  }
165
112
  ) {
166
- if (anchor == null) {
167
- HitResultNode(xPx = view.width / 2f, yPx = view.height / 2f) {
168
- SphereNode(radius = 0.02f)
113
+ for ((_, image) in augmentedImages) {
114
+ AugmentedImageNode(augmentedImage = image) {
115
+ modelInstance?.let { instance ->
116
+ ModelNode(modelInstance = instance, scaleToUnits = 0.1f)
117
+ }
169
118
  }
170
119
  }
171
- anchor?.let { a ->
172
- AnchorNode(anchor = a) {
173
- ModelNode(
174
- modelInstance = modelInstance ?: return@AnchorNode,
175
- scaleToUnits = 0.5f,
176
- isEditable = true
177
- )
178
- }
120
+ }
121
+ }`,
122
+ },
123
+ "ar-cloud-anchor": {
124
+ id: "ar-cloud-anchor",
125
+ title: "AR Cloud Anchor",
126
+ description: "Host and resolve persistent cross-device anchors using ARCore Cloud Anchors.",
127
+ tags: ["ar", "anchor", "cloud-anchor"],
128
+ dependency: "io.github.sceneview:arsceneview:3.3.0",
129
+ prompt: "Create an AR screen that can host a cloud anchor (saving its ID) and resolve it later on another device. Use SceneView `io.github.sceneview:arsceneview:3.3.0`.",
130
+ code: `@Composable
131
+ fun CloudAnchorScreen() {
132
+ val engine = rememberEngine()
133
+ val modelLoader = rememberModelLoader(engine)
134
+ var session by remember { mutableStateOf<Session?>(null) }
135
+ var cloudAnchorNode by remember { mutableStateOf<CloudAnchorNode?>(null) }
136
+
137
+ ARScene(
138
+ modifier = Modifier.fillMaxSize(),
139
+ engine = engine,
140
+ modelLoader = modelLoader,
141
+ sessionConfiguration = { _, config ->
142
+ config.cloudAnchorMode = Config.CloudAnchorMode.ENABLED
143
+ },
144
+ onSessionCreated = { s -> session = s }
145
+ ) {
146
+ cloudAnchorNode?.let { node ->
147
+ CloudAnchorNode(anchor = node.anchor, cloudAnchorId = node.cloudAnchorId)
179
148
  }
180
149
  }
150
+ // Host: CloudAnchorNode(engine, anchor).host(session) { id, state -> ... }
151
+ // Resolve: CloudAnchorNode.resolve(engine, session, cloudAnchorId) { state, node -> ... }
181
152
  }`,
182
153
  },
183
- "ar-augmented-image": {
184
- id: "ar-augmented-image",
185
- title: "AR Augmented Image",
186
- description: "Detects a reference image in the camera feed and overlays a 3D model above it.",
187
- tags: ["ar", "model", "anchor", "image-tracking"],
188
- dependency: "io.github.sceneview:arsceneview:3.1.1",
189
- prompt: "Create an Android Compose AR screen called `AugmentedImageScreen` that detects a printed reference image (from R.drawable.target_image, physical width 15 cm) and places a 3D GLB model from assets/models/overlay.glb above it, scaled to match the image width. The model should disappear when the image is lost. Use SceneView `io.github.sceneview:arsceneview:3.1.1`.",
154
+ "ar-point-cloud": {
155
+ id: "ar-point-cloud",
156
+ title: "AR Point Cloud",
157
+ description: "Visualizes ARCore feature points as 3D spheres with confidence-based filtering.",
158
+ tags: ["ar", "point-cloud"],
159
+ dependency: "io.github.sceneview:arsceneview:3.3.0",
160
+ prompt: "Create an AR screen that visualizes ARCore feature points as small 3D spheres, filtered by confidence. Use SceneView `io.github.sceneview:arsceneview:3.3.0`.",
190
161
  code: `@Composable
191
- fun AugmentedImageScreen() {
162
+ fun PointCloudScreen() {
192
163
  val engine = rememberEngine()
193
164
  val modelLoader = rememberModelLoader(engine)
194
- val context = LocalContext.current
195
- var trackedImages by remember { mutableStateOf(listOf<AugmentedImage>()) }
165
+ var pointCount by remember { mutableIntStateOf(0) }
196
166
 
197
167
  ARScene(
198
168
  modifier = Modifier.fillMaxSize(),
199
169
  engine = engine,
200
170
  modelLoader = modelLoader,
201
- sessionConfiguration = { session, config ->
202
- config.augmentedImageDatabase = AugmentedImageDatabase(session).also { db ->
203
- db.addImage(
204
- "target",
205
- BitmapFactory.decodeResource(context.resources, R.drawable.target_image),
206
- 0.15f
207
- )
208
- }
171
+ planeRenderer = false,
172
+ sessionConfiguration = { _, config ->
173
+ config.lightEstimationMode = Config.LightEstimationMode.DISABLED
209
174
  },
210
175
  onSessionUpdated = { _, frame ->
211
- trackedImages = frame
212
- .getUpdatedTrackables(AugmentedImage::class.java)
213
- .filter { it.trackingState == TrackingState.TRACKING }
176
+ frame.acquirePointCloud()?.use { cloud ->
177
+ pointCount = cloud.ids?.limit() ?: 0
178
+ // Process points: cloud.points buffer has [x, y, z, confidence] per point
179
+ }
214
180
  }
215
181
  ) {
216
- trackedImages.forEach { image ->
217
- AugmentedImageNode(augmentedImage = image) {
218
- rememberModelInstance(modelLoader, "models/overlay.glb")?.let { instance ->
219
- ModelNode(modelInstance = instance, scaleToUnits = image.extentX)
220
- }
182
+ // Render point cloud model instances at detected positions
183
+ }
184
+ }`,
185
+ },
186
+ "gltf-camera": {
187
+ id: "gltf-camera",
188
+ title: "glTF Camera",
189
+ description: "Extracts and uses camera definitions embedded in a glTF file for cinematic viewpoints.",
190
+ tags: ["3d", "model", "camera"],
191
+ dependency: "io.github.sceneview:sceneview:3.3.0",
192
+ prompt: "Create a 3D scene that loads a GLB file containing embedded camera definitions, then uses those cameras for cinematic viewpoints. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
193
+ code: `@Composable
194
+ fun GltfCameraScreen() {
195
+ val engine = rememberEngine()
196
+ val modelLoader = rememberModelLoader(engine)
197
+ val environmentLoader = rememberEnvironmentLoader(engine)
198
+ val modelInstance = rememberModelInstance(modelLoader, "models/scene_with_cameras.glb")
199
+ val cameraNode = rememberCameraNode(engine)
200
+
201
+ Scene(
202
+ modifier = Modifier.fillMaxSize(),
203
+ engine = engine,
204
+ modelLoader = modelLoader,
205
+ cameraNode = cameraNode,
206
+ environment = rememberEnvironment(environmentLoader) {
207
+ environmentLoader.createHDREnvironment("environments/sky_2k.hdr") ?: createEnvironment(environmentLoader)
208
+ }
209
+ ) {
210
+ modelInstance?.let { instance ->
211
+ ModelNode(modelInstance = instance, scaleToUnits = 1.0f)
212
+ }
213
+ }
214
+ }`,
215
+ },
216
+ "camera-manipulator": {
217
+ id: "camera-manipulator",
218
+ title: "Camera Manipulator",
219
+ description: "Orbit, pan, and zoom camera with customizable sensitivity and bounds.",
220
+ tags: ["3d", "camera", "gestures"],
221
+ dependency: "io.github.sceneview:sceneview:3.3.0",
222
+ prompt: "Create a 3D scene with a fully configurable orbit camera — drag to rotate, two-finger pan, pinch to zoom. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
223
+ code: `@Composable
224
+ fun CameraManipulatorScreen() {
225
+ val engine = rememberEngine()
226
+ val modelLoader = rememberModelLoader(engine)
227
+ val cameraNode = rememberCameraNode(engine) {
228
+ position = Position(z = 4f)
229
+ }
230
+
231
+ Scene(
232
+ modifier = Modifier.fillMaxSize(),
233
+ engine = engine,
234
+ modelLoader = modelLoader,
235
+ cameraNode = cameraNode,
236
+ cameraManipulator = rememberCameraManipulator(
237
+ orbitHomePosition = cameraNode.worldPosition,
238
+ targetPosition = Position(0f)
239
+ )
240
+ ) {
241
+ rememberModelInstance(modelLoader, "models/damaged_helmet.glb")?.let { instance ->
242
+ ModelNode(modelInstance = instance, scaleToUnits = 1.0f)
243
+ }
244
+ }
245
+ }`,
246
+ },
247
+ "autopilot-demo": {
248
+ id: "autopilot-demo",
249
+ title: "Autopilot Demo",
250
+ description: "Full autonomous driving HUD with animated car, traffic lights, road, and real-time telemetry overlay.",
251
+ tags: ["3d", "model", "animation", "geometry"],
252
+ dependency: "io.github.sceneview:sceneview:3.3.0",
253
+ prompt: "Create a Tesla FSD-style autopilot visualization with a 3D car on a road, traffic lights, and a HUD overlay showing speed, distance, and status. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
254
+ code: `@Composable
255
+ fun AutopilotScreen() {
256
+ val engine = rememberEngine()
257
+ val modelLoader = rememberModelLoader(engine)
258
+
259
+ Scene(
260
+ modifier = Modifier.fillMaxSize(),
261
+ engine = engine,
262
+ modelLoader = modelLoader
263
+ ) {
264
+ // Road surface
265
+ PlaneNode(engine, size = Size(6f, 0f, 50f),
266
+ position = Position(y = -0.01f))
267
+
268
+ // Ego car
269
+ rememberModelInstance(modelLoader, "models/car.glb")?.let { instance ->
270
+ ModelNode(modelInstance = instance, scaleToUnits = 2f)
271
+ }
272
+
273
+ // Traffic light with state machine
274
+ // See samples/autopilot-demo for the full implementation
275
+ }
276
+ }`,
277
+ },
278
+ "physics-demo": {
279
+ id: "physics-demo",
280
+ title: "Physics Demo",
281
+ description: "Interactive physics simulation with bouncing spheres, gravity, configurable restitution, and colour selection.",
282
+ tags: ["3d", "physics", "geometry", "animation"],
283
+ dependency: "io.github.sceneview:sceneview:3.3.0",
284
+ prompt: "Create a 3D scene where tapping spawns coloured spheres that fall under gravity and bounce off a floor. Add a bounciness slider. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
285
+ code: `@Composable
286
+ fun PhysicsDemoScreen() {
287
+ val engine = rememberEngine()
288
+ val materialLoader = rememberMaterialLoader(engine)
289
+ var restitution by remember { mutableFloatStateOf(0.7f) }
290
+
291
+ Scene(
292
+ modifier = Modifier.fillMaxSize(),
293
+ engine = engine,
294
+ materialLoader = materialLoader
295
+ ) {
296
+ // Floor
297
+ PlaneNode(engine, size = Size(4f, 0f, 4f),
298
+ materialInstance = materialLoader.createColorInstance(Color.DarkGray))
299
+
300
+ // Spawn spheres and attach PhysicsNode
301
+ val sphere = remember(engine) {
302
+ SphereNode(engine, radius = 0.15f).apply {
303
+ position = Position(y = 3f)
221
304
  }
222
305
  }
306
+ PhysicsNode(
307
+ node = sphere,
308
+ restitution = restitution,
309
+ radius = 0.15f
310
+ )
223
311
  }
224
312
  }`,
225
313
  },
226
- "ar-face-filter": {
227
- id: "ar-face-filter",
228
- title: "AR Face Filter",
229
- description: "Front-camera AR that detects faces and renders a 3D mesh material over them.",
230
- tags: ["ar", "face-tracking", "camera"],
231
- dependency: "io.github.sceneview:arsceneview:3.1.1",
232
- prompt: "Create an Android Compose AR screen called `FaceFilterScreen` using the front camera. Detect all visible faces and apply a custom material from assets/materials/face_mask.filamat to the face mesh. Use SceneView `io.github.sceneview:arsceneview:3.1.1` with `Session.Feature.FRONT_CAMERA` and `AugmentedFaceMode.MESH3D`.",
314
+ "dynamic-sky": {
315
+ id: "dynamic-sky",
316
+ title: "Dynamic Sky",
317
+ description: "Time-of-day sun cycle with DynamicSkyNode and atmospheric fog via FogNode.",
318
+ tags: ["3d", "sky", "fog", "environment"],
319
+ dependency: "io.github.sceneview:sceneview:3.3.0",
320
+ prompt: "Create a 3D scene with a time-of-day sun that moves from sunrise through noon to sunset, with atmospheric fog. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
233
321
  code: `@Composable
234
- fun FaceFilterScreen() {
322
+ fun DynamicSkyScreen() {
235
323
  val engine = rememberEngine()
236
324
  val modelLoader = rememberModelLoader(engine)
325
+ var timeOfDay by remember { mutableFloatStateOf(12f) }
326
+
327
+ Scene(
328
+ modifier = Modifier.fillMaxSize(),
329
+ engine = engine,
330
+ modelLoader = modelLoader
331
+ ) {
332
+ DynamicSkyNode(
333
+ timeOfDay = timeOfDay,
334
+ turbidity = 2f,
335
+ sunIntensity = 110_000f
336
+ )
337
+
338
+ rememberModelInstance(modelLoader, "models/scene.glb")?.let { instance ->
339
+ ModelNode(modelInstance = instance)
340
+ }
341
+ }
342
+ // Add a Slider to control timeOfDay from 0 to 24
343
+ }`,
344
+ },
345
+ "line-path": {
346
+ id: "line-path",
347
+ title: "Line & Path",
348
+ description: "Animated 3D line art with sine waves, Lissajous curves, and parameter sliders.",
349
+ tags: ["3d", "lines", "geometry", "animation"],
350
+ dependency: "io.github.sceneview:sceneview:3.3.0",
351
+ prompt: "Create a 3D scene that draws animated parametric curves (sine wave, Lissajous) using PathNode with amplitude and frequency sliders. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
352
+ code: `@Composable
353
+ fun LinePathScreen() {
354
+ val engine = rememberEngine()
237
355
  val materialLoader = rememberMaterialLoader(engine)
238
- var trackedFaces by remember { mutableStateOf(listOf<AugmentedFace>()) }
239
- val faceMaterial = remember(materialLoader) {
240
- materialLoader.createInstance("materials/face_mask.filamat")
356
+ var amplitude by remember { mutableFloatStateOf(1f) }
357
+ var frequency by remember { mutableFloatStateOf(2f) }
358
+
359
+ val points = remember(amplitude, frequency) {
360
+ (0..200).map { i ->
361
+ val t = i / 200f * Math.PI.toFloat() * 4
362
+ Position(x = t * 0.5f - 3f, y = sin(t * frequency) * amplitude, z = 0f)
363
+ }
241
364
  }
242
365
 
243
- ARScene(
366
+ Scene(modifier = Modifier.fillMaxSize(), engine = engine, materialLoader = materialLoader) {
367
+ val path = remember(engine, points) {
368
+ PathNode(engine = engine, points = points)
369
+ }
370
+ Node(node = path)
371
+ }
372
+ // Add Sliders for amplitude and frequency
373
+ }`,
374
+ },
375
+ "text-labels": {
376
+ id: "text-labels",
377
+ title: "Text Labels",
378
+ description: "Camera-facing 3D text labels (TextNode + BillboardNode) with interactive label cycling.",
379
+ tags: ["3d", "text", "geometry"],
380
+ dependency: "io.github.sceneview:sceneview:3.3.0",
381
+ prompt: "Create a 3D scene with floating text labels that always face the camera. Labels show planet names and can be tapped to cycle display modes. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
382
+ code: `@Composable
383
+ fun TextLabelsScreen() {
384
+ val engine = rememberEngine()
385
+ val materialLoader = rememberMaterialLoader(engine)
386
+ var cameraPos by remember { mutableStateOf(Position()) }
387
+
388
+ Scene(
389
+ modifier = Modifier.fillMaxSize(),
390
+ engine = engine,
391
+ materialLoader = materialLoader,
392
+ onFrame = { cameraPos = cameraNode.worldPosition }
393
+ ) {
394
+ TextNode(
395
+ materialLoader = materialLoader,
396
+ text = "Earth",
397
+ fontSize = 48f,
398
+ textColor = android.graphics.Color.WHITE,
399
+ backgroundColor = 0xCC000000.toInt(),
400
+ widthMeters = 0.6f,
401
+ heightMeters = 0.2f,
402
+ cameraPositionProvider = { cameraPos }
403
+ )
404
+ }
405
+ }`,
406
+ },
407
+ "reflection-probe": {
408
+ id: "reflection-probe",
409
+ title: "Reflection Probe",
410
+ description: "Zone-based IBL overrides with material picker (Chrome, Gold, Copper, Rough) and probe toggle.",
411
+ tags: ["3d", "reflection", "environment", "model"],
412
+ dependency: "io.github.sceneview:sceneview:3.3.0",
413
+ prompt: "Create a 3D scene with a metallic sphere and a ReflectionProbeNode that overrides the IBL. Add a material picker to switch between Chrome, Gold, Copper, and Rough. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
414
+ code: `@Composable
415
+ fun ReflectionProbeScreen() {
416
+ val engine = rememberEngine()
417
+ val modelLoader = rememberModelLoader(engine)
418
+ val environmentLoader = rememberEnvironmentLoader(engine)
419
+ var cameraPosition by remember { mutableStateOf(Position()) }
420
+ val environment = rememberEnvironment(environmentLoader) {
421
+ environmentLoader.createHDREnvironment("environments/studio.hdr") ?: createEnvironment(environmentLoader)
422
+ }
423
+
424
+ Scene(
244
425
  modifier = Modifier.fillMaxSize(),
245
426
  engine = engine,
246
427
  modelLoader = modelLoader,
247
- sessionFeatures = setOf(Session.Feature.FRONT_CAMERA),
248
- sessionConfiguration = { _, config ->
249
- config.augmentedFaceMode = Config.AugmentedFaceMode.MESH3D
250
- },
251
- onSessionUpdated = { session, _ ->
252
- trackedFaces = session
253
- .getAllTrackables(AugmentedFace::class.java)
254
- .filter { it.trackingState == TrackingState.TRACKING }
428
+ environment = environment,
429
+ onFrame = { cameraPosition = cameraNode.worldPosition }
430
+ ) {
431
+ ReflectionProbeNode(
432
+ filamentScene = scene,
433
+ environment = environment,
434
+ cameraPosition = cameraPosition
435
+ )
436
+
437
+ rememberModelInstance(modelLoader, "models/sphere.glb")?.let { instance ->
438
+ ModelNode(modelInstance = instance, scaleToUnits = 1.0f)
255
439
  }
440
+ }
441
+ }`,
442
+ },
443
+ "post-processing": {
444
+ id: "post-processing",
445
+ title: "Post-Processing",
446
+ description: "Real-time post-processing effects: bloom, vignette, tone mapping, FXAA, and SSAO controls.",
447
+ tags: ["3d", "post-processing", "environment"],
448
+ dependency: "io.github.sceneview:sceneview:3.3.0",
449
+ prompt: "Create a 3D scene with interactive post-processing controls for bloom, vignette, tone mapping, FXAA, and SSAO. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
450
+ code: `@Composable
451
+ fun PostProcessingScreen() {
452
+ val engine = rememberEngine()
453
+ val modelLoader = rememberModelLoader(engine)
454
+ val view = rememberView(engine)
455
+ var bloomStrength by remember { mutableFloatStateOf(0.1f) }
456
+
457
+ Scene(
458
+ modifier = Modifier.fillMaxSize(),
459
+ engine = engine,
460
+ modelLoader = modelLoader,
461
+ view = view
256
462
  ) {
257
- trackedFaces.forEach { face ->
258
- AugmentedFaceNode(augmentedFace = face, meshMaterialInstance = faceMaterial)
463
+ rememberModelInstance(modelLoader, "models/damaged_helmet.glb")?.let { instance ->
464
+ ModelNode(modelInstance = instance, scaleToUnits = 1.0f)
465
+ }
466
+ }
467
+ // Configure view.bloomOptions, view.vignetteOptions, etc.
468
+ // See samples/post-processing for full interactive controls
469
+ }`,
470
+ },
471
+ // ─── iOS Samples ────────────────────────────────────────────────────────────
472
+ "ios-model-viewer": {
473
+ id: "ios-model-viewer",
474
+ title: "iOS 3D Model Viewer",
475
+ description: "SwiftUI 3D scene with a USDZ model, IBL environment, orbit camera, and animation playback.",
476
+ tags: ["3d", "model", "environment", "camera", "animation", "ios", "swift"],
477
+ dependency: "https://github.com/SceneView/sceneview — from: \"3.3.0\"",
478
+ spmDependency: "https://github.com/SceneView/sceneview",
479
+ prompt: "Create a SwiftUI screen that loads a USDZ model and displays it with IBL lighting, orbit camera, and animation playback. Use SceneViewSwift.",
480
+ language: "swift",
481
+ code: `import SwiftUI
482
+ import SceneViewSwift
483
+ import RealityKit
484
+
485
+ struct ModelViewerScreen: View {
486
+ @State private var model: ModelNode?
487
+
488
+ var body: some View {
489
+ SceneView { root in
490
+ if let model {
491
+ root.addChild(model.entity)
492
+ }
493
+ }
494
+ .environment(.studio)
495
+ .cameraControls(.orbit)
496
+ .onEntityTapped { entity in
497
+ print("Tapped: \\(entity)")
498
+ }
499
+ .task {
500
+ do {
501
+ model = try await ModelNode.load("models/car.usdz")
502
+ model?.scaleToUnits(1.0)
503
+ model?.playAllAnimations()
504
+ } catch {
505
+ print("Failed to load model: \\(error)")
506
+ }
507
+ }
508
+ }
509
+ }`,
510
+ },
511
+ "ios-ar-model-viewer": {
512
+ id: "ios-ar-model-viewer",
513
+ title: "iOS AR Tap-to-Place Model Viewer",
514
+ description: "AR scene with plane detection. Tap a surface to place a 3D model using ARKit + RealityKit.",
515
+ tags: ["ar", "model", "anchor", "plane-detection", "placement", "ios", "swift"],
516
+ dependency: "https://github.com/SceneView/sceneview — from: \"3.3.0\"",
517
+ spmDependency: "https://github.com/SceneView/sceneview",
518
+ prompt: "Create an iOS AR screen that detects surfaces and lets the user tap to place a USDZ model. Use SceneViewSwift.",
519
+ language: "swift",
520
+ code: `import SwiftUI
521
+ import SceneViewSwift
522
+ import RealityKit
523
+
524
+ struct ARModelViewerScreen: View {
525
+ @State private var model: ModelNode?
526
+
527
+ var body: some View {
528
+ ARSceneView(
529
+ planeDetection: .horizontal,
530
+ showCoachingOverlay: true,
531
+ onTapOnPlane: { position, arView in
532
+ guard let model else { return }
533
+ let anchor = AnchorNode.world(position: position)
534
+ let clone = model.entity.clone(recursive: true)
535
+ clone.scale = .init(repeating: 0.3)
536
+ anchor.add(clone)
537
+ arView.scene.addAnchor(anchor.entity)
538
+ }
539
+ )
540
+ .edgesIgnoringSafeArea(.all)
541
+ .task {
542
+ do {
543
+ model = try await ModelNode.load("models/robot.usdz")
544
+ } catch {
545
+ print("Failed to load model: \\(error)")
546
+ }
547
+ }
548
+ }
549
+ }`,
550
+ },
551
+ "ios-ar-augmented-image": {
552
+ id: "ios-ar-augmented-image",
553
+ title: "iOS AR Augmented Image",
554
+ description: "Detects reference images in the camera feed and overlays 3D content above them using ARKit.",
555
+ tags: ["ar", "model", "image-tracking", "ios", "swift"],
556
+ dependency: "https://github.com/SceneView/sceneview — from: \"3.3.0\"",
557
+ spmDependency: "https://github.com/SceneView/sceneview",
558
+ prompt: "Create an iOS AR screen that detects a printed reference image and places a 3D model above it. Use SceneViewSwift.",
559
+ language: "swift",
560
+ code: `import SwiftUI
561
+ import SceneViewSwift
562
+ import RealityKit
563
+ import ARKit
564
+
565
+ struct AugmentedImageScreen: View {
566
+ var body: some View {
567
+ ARSceneView(
568
+ planeDetection: .horizontal,
569
+ imageTrackingDatabase: AugmentedImageNode.createImageDatabase([
570
+ AugmentedImageNode.ReferenceImage(
571
+ name: "poster",
572
+ image: UIImage(named: "poster_reference")!,
573
+ physicalWidth: 0.3 // 30 cm
574
+ )
575
+ ]),
576
+ onImageDetected: { imageName, anchor, arView in
577
+ // Place a spinning cube above the detected image
578
+ let cube = GeometryNode.cube(size: 0.08, color: .systemBlue)
579
+ .position(.init(x: 0, y: 0.06, z: 0))
580
+ anchor.add(cube.entity)
581
+ arView.scene.addAnchor(anchor.entity)
582
+ print("Detected image: \\(imageName)")
583
+ }
584
+ )
585
+ .edgesIgnoringSafeArea(.all)
586
+ }
587
+ }`,
588
+ },
589
+ "ios-geometry-shapes": {
590
+ id: "ios-geometry-shapes",
591
+ title: "iOS Procedural Geometry",
592
+ description: "Procedural geometry shapes — cube, sphere, cylinder, cone, and plane — with PBR materials.",
593
+ tags: ["3d", "geometry", "ios", "swift"],
594
+ dependency: "https://github.com/SceneView/sceneview — from: \"3.3.0\"",
595
+ spmDependency: "https://github.com/SceneView/sceneview",
596
+ prompt: "Create a SwiftUI scene showing procedural geometry shapes (cube, sphere, cylinder, cone, plane) with different materials. Use SceneViewSwift.",
597
+ language: "swift",
598
+ code: `import SwiftUI
599
+ import SceneViewSwift
600
+ import RealityKit
601
+
602
+ struct GeometryShapesScreen: View {
603
+ var body: some View {
604
+ SceneView { root in
605
+ // Red cube
606
+ let cube = GeometryNode.cube(size: 0.3, color: .red, cornerRadius: 0.02)
607
+ .position(.init(x: -0.8, y: 0, z: 0))
608
+ root.addChild(cube.entity)
609
+
610
+ // Metallic sphere
611
+ let sphere = GeometryNode.sphere(
612
+ radius: 0.2,
613
+ material: .pbr(color: .gray, metallic: 1.0, roughness: 0.2)
614
+ )
615
+ .position(.init(x: -0.3, y: 0, z: 0))
616
+ root.addChild(sphere.entity)
617
+
618
+ // Green cylinder
619
+ let cylinder = GeometryNode.cylinder(
620
+ radius: 0.15, height: 0.4, color: .green
621
+ )
622
+ .position(.init(x: 0.2, y: 0, z: 0))
623
+ root.addChild(cylinder.entity)
624
+
625
+ // Blue cone
626
+ let cone = GeometryNode.cone(
627
+ height: 0.4, radius: 0.2, color: .systemBlue
628
+ )
629
+ .position(.init(x: 0.7, y: 0, z: 0))
630
+ root.addChild(cone.entity)
631
+
632
+ // Floor plane
633
+ let floor = GeometryNode.plane(
634
+ width: 3.0, depth: 3.0, color: .darkGray
635
+ )
636
+ .position(.init(x: 0, y: -0.25, z: 0))
637
+ root.addChild(floor.entity)
638
+ }
639
+ .cameraControls(.orbit)
640
+ }
641
+ }`,
642
+ },
643
+ "ios-lighting": {
644
+ id: "ios-lighting",
645
+ title: "iOS Lighting",
646
+ description: "Directional, point, and spot lights with configurable intensity, color, and shadows.",
647
+ tags: ["3d", "lighting", "environment", "ios", "swift"],
648
+ dependency: "https://github.com/SceneView/sceneview — from: \"3.3.0\"",
649
+ spmDependency: "https://github.com/SceneView/sceneview",
650
+ prompt: "Create a SwiftUI 3D scene with directional, point, and spot lights illuminating geometry. Use SceneViewSwift.",
651
+ language: "swift",
652
+ code: `import SwiftUI
653
+ import SceneViewSwift
654
+ import RealityKit
655
+
656
+ struct LightingScreen: View {
657
+ var body: some View {
658
+ SceneView { root in
659
+ // Ground plane
660
+ let floor = GeometryNode.plane(
661
+ width: 4.0, depth: 4.0, color: .lightGray
662
+ )
663
+ root.addChild(floor.entity)
664
+
665
+ // Metallic sphere to show reflections
666
+ let sphere = GeometryNode.sphere(
667
+ radius: 0.3,
668
+ material: .pbr(color: .white, metallic: 0.8, roughness: 0.3)
669
+ )
670
+ .position(.init(x: 0, y: 0.3, z: 0))
671
+ root.addChild(sphere.entity)
672
+
673
+ // Directional light (sun) with shadows
674
+ let sun = LightNode.directional(
675
+ color: .warm,
676
+ intensity: 1500,
677
+ castsShadow: true
678
+ )
679
+ sun.entity.look(at: .zero, from: [3, 5, 3], relativeTo: nil)
680
+ root.addChild(sun.entity)
681
+
682
+ // Point light (red)
683
+ let pointLight = LightNode.point(
684
+ color: .custom(r: 1.0, g: 0.2, b: 0.2),
685
+ intensity: 5000,
686
+ attenuationRadius: 5.0
687
+ )
688
+ .position(.init(x: -1.0, y: 1.0, z: 0.5))
689
+ root.addChild(pointLight.entity)
690
+
691
+ // Spot light (blue)
692
+ let spotLight = LightNode.spot(
693
+ color: .custom(r: 0.2, g: 0.4, b: 1.0),
694
+ intensity: 8000,
695
+ innerAngle: .pi / 8,
696
+ outerAngle: .pi / 4,
697
+ attenuationRadius: 8.0
698
+ )
699
+ .position(.init(x: 1.0, y: 2.0, z: 0.5))
700
+ spotLight.entity.look(at: .zero, from: spotLight.entity.position, relativeTo: nil)
701
+ root.addChild(spotLight.entity)
702
+ }
703
+ .cameraControls(.orbit)
704
+ }
705
+ }`,
706
+ },
707
+ "ios-physics": {
708
+ id: "ios-physics",
709
+ title: "iOS Physics Demo",
710
+ description: "Interactive physics simulation with bouncing spheres, gravity, and configurable restitution.",
711
+ tags: ["3d", "physics", "geometry", "ios", "swift"],
712
+ dependency: "https://github.com/SceneView/sceneview — from: \"3.3.0\"",
713
+ spmDependency: "https://github.com/SceneView/sceneview",
714
+ prompt: "Create a SwiftUI 3D scene where tapping spawns coloured spheres that fall under gravity and bounce off a floor. Use SceneViewSwift.",
715
+ language: "swift",
716
+ code: `import SwiftUI
717
+ import SceneViewSwift
718
+ import RealityKit
719
+
720
+ struct PhysicsDemoScreen: View {
721
+ @State private var sphereColors: [SimpleMaterial.Color] = [
722
+ .red, .blue, .green, .orange, .purple, .yellow
723
+ ]
724
+ @State private var spawnCount = 0
725
+
726
+ var body: some View {
727
+ SceneView { root in
728
+ // Static floor
729
+ let floor = GeometryNode.plane(
730
+ width: 4.0, depth: 4.0, color: .darkGray
731
+ )
732
+ PhysicsNode.static(floor.entity, restitution: 0.8)
733
+ root.addChild(floor.entity)
734
+
735
+ // Spawn initial spheres at different heights
736
+ for i in 0..<6 {
737
+ let color = sphereColors[i % sphereColors.count]
738
+ let sphere = GeometryNode.sphere(radius: 0.15, color: color)
739
+ .position(.init(
740
+ x: Float(i % 3) * 0.5 - 0.5,
741
+ y: Float(i / 3) * 1.0 + 1.5,
742
+ z: 0
743
+ ))
744
+ .withGroundingShadow()
745
+ PhysicsNode.dynamic(
746
+ sphere.entity,
747
+ mass: 1.0,
748
+ restitution: 0.7,
749
+ friction: 0.3
750
+ )
751
+ root.addChild(sphere.entity)
752
+ }
753
+
754
+ // Walls to keep spheres in bounds
755
+ let wallLeft = GeometryNode.cube(size: 0.05, color: .clear)
756
+ .position(.init(x: -2, y: 1, z: 0))
757
+ wallLeft.entity.scale = .init(x: 0.05, y: 4, z: 4)
758
+ PhysicsNode.static(wallLeft.entity)
759
+ root.addChild(wallLeft.entity)
760
+
761
+ let wallRight = GeometryNode.cube(size: 0.05, color: .clear)
762
+ .position(.init(x: 2, y: 1, z: 0))
763
+ wallRight.entity.scale = .init(x: 0.05, y: 4, z: 4)
764
+ PhysicsNode.static(wallRight.entity)
765
+ root.addChild(wallRight.entity)
766
+ }
767
+ .cameraControls(.orbit)
768
+ }
769
+ }`,
770
+ },
771
+ "ios-text-labels": {
772
+ id: "ios-text-labels",
773
+ title: "iOS 3D Text Labels",
774
+ description: "Camera-facing 3D text labels using TextNode and BillboardNode for always-facing-camera behavior.",
775
+ tags: ["3d", "text", "geometry", "ios", "swift"],
776
+ dependency: "https://github.com/SceneView/sceneview — from: \"3.3.0\"",
777
+ spmDependency: "https://github.com/SceneView/sceneview",
778
+ prompt: "Create a SwiftUI 3D scene with floating text labels that always face the camera, showing planet names. Use SceneViewSwift.",
779
+ language: "swift",
780
+ code: `import SwiftUI
781
+ import SceneViewSwift
782
+ import RealityKit
783
+
784
+ struct TextLabelsScreen: View {
785
+ let planets: [(name: String, color: SimpleMaterial.Color, position: SIMD3<Float>)] = [
786
+ ("Earth", .systemBlue, .init(x: -1.0, y: 0, z: 0)),
787
+ ("Mars", .systemRed, .init(x: 0, y: 0, z: 0)),
788
+ ("Venus", .systemOrange, .init(x: 1.0, y: 0, z: 0)),
789
+ ]
790
+
791
+ var body: some View {
792
+ SceneView { root in
793
+ for planet in planets {
794
+ // Planet sphere
795
+ let sphere = GeometryNode.sphere(
796
+ radius: 0.2, color: planet.color
797
+ )
798
+ .position(planet.position)
799
+ root.addChild(sphere.entity)
800
+
801
+ // Billboard text label above the planet
802
+ let label = BillboardNode.text(
803
+ planet.name,
804
+ fontSize: 0.04,
805
+ color: .white
806
+ )
807
+ .position(.init(
808
+ x: planet.position.x,
809
+ y: planet.position.y + 0.35,
810
+ z: planet.position.z
811
+ ))
812
+ root.addChild(label.entity)
813
+ }
814
+ }
815
+ .cameraControls(.orbit)
816
+ }
817
+ }`,
818
+ },
819
+ "ios-video-player": {
820
+ id: "ios-video-player",
821
+ title: "iOS Video on 3D Surface",
822
+ description: "Video playback on a 3D plane using VideoNode with play/pause controls.",
823
+ tags: ["3d", "video", "ios", "swift"],
824
+ dependency: "https://github.com/SceneView/sceneview — from: \"3.3.0\"",
825
+ spmDependency: "https://github.com/SceneView/sceneview",
826
+ prompt: "Create a SwiftUI 3D scene with a video playing on a floating 3D plane. Include play/pause controls. Use SceneViewSwift.",
827
+ language: "swift",
828
+ code: `import SwiftUI
829
+ import SceneViewSwift
830
+ import RealityKit
831
+
832
+ struct VideoPlayerScreen: View {
833
+ @State private var videoNode: VideoNode?
834
+ @State private var isPlaying = false
835
+
836
+ var body: some View {
837
+ ZStack {
838
+ SceneView { root in
839
+ if let videoNode {
840
+ root.addChild(videoNode.entity)
841
+ }
842
+ }
843
+ .cameraControls(.orbit)
844
+ .onAppear {
845
+ videoNode = VideoNode.load(
846
+ "videos/intro.mp4",
847
+ width: 1.6,
848
+ height: 0.9,
849
+ loop: true
850
+ )
851
+ .position(.init(x: 0, y: 0.5, z: -2))
852
+ }
853
+
854
+ // Play/Pause overlay
855
+ VStack {
856
+ Spacer()
857
+ HStack(spacing: 30) {
858
+ Button(action: {
859
+ videoNode?.play()
860
+ isPlaying = true
861
+ }) {
862
+ Image(systemName: "play.fill")
863
+ .font(.title)
864
+ }
865
+ Button(action: {
866
+ videoNode?.pause()
867
+ isPlaying = false
868
+ }) {
869
+ Image(systemName: "pause.fill")
870
+ .font(.title)
871
+ }
872
+ Button(action: {
873
+ videoNode?.stop()
874
+ isPlaying = false
875
+ }) {
876
+ Image(systemName: "stop.fill")
877
+ .font(.title)
878
+ }
879
+ }
880
+ .padding()
881
+ .background(.ultraThinMaterial)
882
+ .cornerRadius(16)
883
+ .padding(.bottom, 40)
884
+ }
259
885
  }
260
886
  }
261
887
  }`,