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/LICENSE +21 -0
- package/README.md +208 -66
- package/dist/guides.js +603 -0
- package/dist/index.js +291 -15
- package/dist/issues.js +2 -2
- package/dist/migration.js +3 -3
- package/dist/samples.js +784 -158
- package/dist/validator.js +287 -3
- package/llms.txt +1746 -19
- package/package.json +11 -5
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
|
|
6
|
-
tags: ["3d", "model", "environment", "camera"],
|
|
7
|
-
dependency: "io.github.sceneview:sceneview:3.
|
|
8
|
-
prompt: "Create an Android Compose screen
|
|
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/
|
|
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
|
-
"
|
|
37
|
-
id: "
|
|
38
|
-
title: "
|
|
39
|
-
description: "
|
|
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.
|
|
98
|
-
prompt: "Create an
|
|
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
|
|
44
|
+
fun ARModelViewerScreen() {
|
|
101
45
|
val engine = rememberEngine()
|
|
102
46
|
val modelLoader = rememberModelLoader(engine)
|
|
103
|
-
val modelInstance = rememberModelInstance(modelLoader, "models/
|
|
104
|
-
var
|
|
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
|
-
|
|
63
|
+
anchor = hitResult.createAnchor()
|
|
121
64
|
true
|
|
122
65
|
}
|
|
123
66
|
) {
|
|
124
|
-
|
|
125
|
-
AnchorNode(anchor =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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-
|
|
137
|
-
id: "ar-
|
|
138
|
-
title: "AR
|
|
139
|
-
description: "
|
|
140
|
-
tags: ["ar", "model", "
|
|
141
|
-
dependency: "io.github.sceneview:arsceneview:3.
|
|
142
|
-
prompt: "Create an
|
|
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
|
|
89
|
+
fun AugmentedImageScreen() {
|
|
145
90
|
val engine = rememberEngine()
|
|
146
91
|
val modelLoader = rememberModelLoader(engine)
|
|
147
|
-
val modelInstance = rememberModelInstance(modelLoader, "models/
|
|
148
|
-
var
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
99
|
+
sessionConfiguration = { session, config ->
|
|
100
|
+
config.addAugmentedImage(
|
|
101
|
+
session, "rabbit",
|
|
102
|
+
assets.open("augmentedimages/rabbit.jpg").use(BitmapFactory::decodeStream)
|
|
103
|
+
)
|
|
159
104
|
},
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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-
|
|
184
|
-
id: "ar-
|
|
185
|
-
title: "AR
|
|
186
|
-
description: "
|
|
187
|
-
tags: ["ar", "
|
|
188
|
-
dependency: "io.github.sceneview:arsceneview:3.
|
|
189
|
-
prompt: "Create an
|
|
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
|
|
162
|
+
fun PointCloudScreen() {
|
|
192
163
|
val engine = rememberEngine()
|
|
193
164
|
val modelLoader = rememberModelLoader(engine)
|
|
194
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
212
|
-
.
|
|
213
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
"
|
|
227
|
-
id: "
|
|
228
|
-
title: "
|
|
229
|
-
description: "
|
|
230
|
-
tags: ["
|
|
231
|
-
dependency: "io.github.sceneview:
|
|
232
|
-
prompt: "Create
|
|
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
|
|
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
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
258
|
-
|
|
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
|
}`,
|