sceneview-mcp 3.6.2 → 3.6.4

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.
@@ -0,0 +1,483 @@
1
+ /**
2
+ * generate-environment.ts
3
+ *
4
+ * Generates code for HDR environment setup, dynamic sky, and lighting configurations.
5
+ */
6
+ export const ENVIRONMENT_TYPES = [
7
+ "hdr-environment",
8
+ "dynamic-sky",
9
+ "studio-lighting",
10
+ "outdoor-lighting",
11
+ "night-scene",
12
+ "ar-lighting",
13
+ ];
14
+ const ANDROID_ENVIRONMENTS = {
15
+ "hdr-environment": {
16
+ description: "Load an HDR environment map for physically-based image-based lighting",
17
+ code: `@Composable
18
+ fun HDREnvironmentScreen() {
19
+ val engine = rememberEngine()
20
+ val modelLoader = rememberModelLoader(engine)
21
+ val environmentLoader = rememberEnvironmentLoader(engine)
22
+
23
+ val modelInstance = rememberModelInstance(modelLoader, "models/helmet.glb")
24
+
25
+ SceneView(
26
+ modifier = Modifier.fillMaxSize(),
27
+ engine = engine,
28
+ modelLoader = modelLoader,
29
+ environment = rememberEnvironment(environmentLoader) {
30
+ environmentLoader.createHDREnvironment("environments/sky_2k.hdr")
31
+ ?: createEnvironment(environmentLoader)
32
+ },
33
+ mainLightNode = rememberMainLightNode(engine) {
34
+ intensity = 100_000f
35
+ },
36
+ cameraManipulator = rememberCameraManipulator()
37
+ ) {
38
+ modelInstance?.let { instance ->
39
+ ModelNode(
40
+ modelInstance = instance,
41
+ scaleToUnits = 1.0f,
42
+ centerOrigin = Position(0f, 0f, 0f)
43
+ )
44
+ }
45
+ }
46
+ }`,
47
+ notes: [
48
+ "Place HDR files in `src/main/assets/environments/`.",
49
+ "Use 2K HDR files for mobile (4K wastes GPU memory).",
50
+ "HDR environment provides both skybox (background) and IBL (reflections).",
51
+ "Without IBL, metallic surfaces appear black.",
52
+ ],
53
+ },
54
+ "dynamic-sky": {
55
+ description: "Time-of-day dynamic sky with sun simulation",
56
+ code: `@Composable
57
+ fun DynamicSkyScreen() {
58
+ val engine = rememberEngine()
59
+ val modelLoader = rememberModelLoader(engine)
60
+ val modelInstance = rememberModelInstance(modelLoader, "models/building.glb")
61
+
62
+ var sunAngle by remember { mutableFloatStateOf(45f) }
63
+
64
+ Column(modifier = Modifier.fillMaxSize()) {
65
+ SceneView(
66
+ modifier = Modifier.weight(1f).fillMaxWidth(),
67
+ engine = engine,
68
+ modelLoader = modelLoader,
69
+ cameraManipulator = rememberCameraManipulator()
70
+ ) {
71
+ // DynamicSkyNode simulates a procedural sky with sun position
72
+ DynamicSkyNode(
73
+ engine = engine,
74
+ sunAngle = sunAngle,
75
+ turbidity = 4.0f, // atmospheric haze (2=clear, 10=overcast)
76
+ groundAlbedo = 0.3f // ground reflectance
77
+ )
78
+
79
+ modelInstance?.let { instance ->
80
+ ModelNode(
81
+ modelInstance = instance,
82
+ scaleToUnits = 2.0f,
83
+ centerOrigin = Position(0f, 0f, 0f)
84
+ )
85
+ }
86
+
87
+ LightNode(
88
+ engine = engine,
89
+ type = LightManager.Type.SUN,
90
+ apply = {
91
+ intensity(110_000f)
92
+ castShadows(true)
93
+ direction(
94
+ -kotlin.math.cos(Math.toRadians(sunAngle.toDouble())).toFloat(),
95
+ -kotlin.math.sin(Math.toRadians(sunAngle.toDouble())).toFloat(),
96
+ 0f
97
+ )
98
+ }
99
+ )
100
+ }
101
+
102
+ Slider(
103
+ value = sunAngle,
104
+ onValueChange = { sunAngle = it },
105
+ valueRange = 0f..180f,
106
+ modifier = Modifier.padding(16.dp)
107
+ )
108
+ Text(
109
+ "Sun angle: \${sunAngle.toInt()}°",
110
+ modifier = Modifier.padding(horizontal = 16.dp),
111
+ color = Color.White
112
+ )
113
+ }
114
+ }`,
115
+ notes: [
116
+ "DynamicSkyNode is a SceneScope composable — must be inside SceneView { }.",
117
+ "sunAngle: 0° = sunrise, 90° = noon, 180° = sunset.",
118
+ "turbidity: 2 = crystal clear, 4 = average, 10 = overcast/hazy.",
119
+ "Sync the LightNode direction with the sun angle for consistent shadows.",
120
+ ],
121
+ },
122
+ "studio-lighting": {
123
+ description: "Three-point studio lighting setup for product visualization",
124
+ code: `@Composable
125
+ fun StudioLightingScreen() {
126
+ val engine = rememberEngine()
127
+ val modelLoader = rememberModelLoader(engine)
128
+ val environmentLoader = rememberEnvironmentLoader(engine)
129
+ val modelInstance = rememberModelInstance(modelLoader, "models/product.glb")
130
+
131
+ SceneView(
132
+ modifier = Modifier.fillMaxSize(),
133
+ engine = engine,
134
+ modelLoader = modelLoader,
135
+ environment = rememberEnvironment(environmentLoader) {
136
+ environmentLoader.createHDREnvironment("environments/studio_2k.hdr")
137
+ ?: createEnvironment(environmentLoader)
138
+ },
139
+ cameraManipulator = rememberCameraManipulator()
140
+ ) {
141
+ // Key light — main illumination from upper-left
142
+ LightNode(
143
+ engine = engine,
144
+ type = LightManager.Type.DIRECTIONAL,
145
+ apply = {
146
+ intensity(120_000f)
147
+ direction(-0.5f, -1f, -0.5f)
148
+ castShadows(true)
149
+ color(1f, 0.98f, 0.95f) // slightly warm
150
+ }
151
+ )
152
+
153
+ // Fill light — softer from the right
154
+ LightNode(
155
+ engine = engine,
156
+ type = LightManager.Type.DIRECTIONAL,
157
+ apply = {
158
+ intensity(40_000f)
159
+ direction(0.7f, -0.5f, -0.3f)
160
+ castShadows(false)
161
+ color(0.9f, 0.95f, 1f) // slightly cool
162
+ }
163
+ )
164
+
165
+ // Rim/back light — highlights edges
166
+ LightNode(
167
+ engine = engine,
168
+ type = LightManager.Type.DIRECTIONAL,
169
+ apply = {
170
+ intensity(60_000f)
171
+ direction(0f, -0.3f, 1f)
172
+ castShadows(false)
173
+ }
174
+ )
175
+
176
+ modelInstance?.let { instance ->
177
+ ModelNode(
178
+ modelInstance = instance,
179
+ scaleToUnits = 1.0f,
180
+ centerOrigin = Position(0f, 0f, 0f)
181
+ )
182
+ }
183
+ }
184
+ }`,
185
+ notes: [
186
+ "Three-point lighting: key (main), fill (secondary), rim (edge highlight).",
187
+ "Key light at ~120K lux, fill at ~40K, rim at ~60K for product photography look.",
188
+ "Use a studio HDR environment for realistic reflections on metallic surfaces.",
189
+ "Adjust light colors for warm/cool mood (warm key + cool fill is classic).",
190
+ ],
191
+ },
192
+ "outdoor-lighting": {
193
+ description: "Outdoor scene with sunlight, sky, and atmospheric effects",
194
+ code: `@Composable
195
+ fun OutdoorLightingScreen() {
196
+ val engine = rememberEngine()
197
+ val modelLoader = rememberModelLoader(engine)
198
+ val environmentLoader = rememberEnvironmentLoader(engine)
199
+ val modelInstance = rememberModelInstance(modelLoader, "models/car.glb")
200
+
201
+ SceneView(
202
+ modifier = Modifier.fillMaxSize(),
203
+ engine = engine,
204
+ modelLoader = modelLoader,
205
+ environment = rememberEnvironment(environmentLoader) {
206
+ environmentLoader.createHDREnvironment("environments/outdoor_2k.hdr")
207
+ ?: createEnvironment(environmentLoader)
208
+ },
209
+ cameraManipulator = rememberCameraManipulator()
210
+ ) {
211
+ // Sun light
212
+ LightNode(
213
+ engine = engine,
214
+ type = LightManager.Type.SUN,
215
+ apply = {
216
+ intensity(110_000f)
217
+ direction(0f, -1f, -0.5f) // high noon, slightly forward
218
+ castShadows(true)
219
+ color(1f, 0.96f, 0.9f) // warm sunlight
220
+ }
221
+ )
222
+
223
+ modelInstance?.let { instance ->
224
+ ModelNode(
225
+ modelInstance = instance,
226
+ scaleToUnits = 2.0f,
227
+ centerOrigin = Position(0f, 0f, 0f)
228
+ )
229
+ }
230
+
231
+ // Ground plane for shadow reception
232
+ val materialLoader = rememberMaterialLoader(engine)
233
+ val groundMat = remember(materialLoader) {
234
+ materialLoader.createColorInstance(Color(0.3f, 0.35f, 0.25f), roughness = 0.9f)
235
+ }
236
+ PlaneNode(
237
+ size = Size(20f, 20f),
238
+ materialInstance = groundMat
239
+ )
240
+ }
241
+ }`,
242
+ notes: [
243
+ "Use LightManager.Type.SUN for outdoor scenes (physically-based sun model).",
244
+ "Sun intensity of ~110,000 lux matches real-world daylight.",
245
+ "Add a ground plane for shadow reception.",
246
+ "Use an outdoor HDR environment for sky reflections.",
247
+ ],
248
+ },
249
+ "night-scene": {
250
+ description: "Night/mood scene with point lights and dim ambient",
251
+ code: `@Composable
252
+ fun NightSceneScreen() {
253
+ val engine = rememberEngine()
254
+ val modelLoader = rememberModelLoader(engine)
255
+ val modelInstance = rememberModelInstance(modelLoader, "models/scene.glb")
256
+
257
+ SceneView(
258
+ modifier = Modifier.fillMaxSize(),
259
+ engine = engine,
260
+ modelLoader = modelLoader,
261
+ cameraManipulator = rememberCameraManipulator()
262
+ ) {
263
+ // Warm point light (like a lamp/candle)
264
+ LightNode(
265
+ engine = engine,
266
+ type = LightManager.Type.POINT,
267
+ apply = {
268
+ intensity(50_000f)
269
+ falloff(8f)
270
+ castShadows(true)
271
+ color(1f, 0.8f, 0.5f) // warm orange
272
+ },
273
+ position = Position(1f, 2f, 0f)
274
+ )
275
+
276
+ // Cool accent light
277
+ LightNode(
278
+ engine = engine,
279
+ type = LightManager.Type.SPOT,
280
+ apply = {
281
+ intensity(30_000f)
282
+ falloff(10f)
283
+ castShadows(false)
284
+ color(0.5f, 0.7f, 1f) // cool blue
285
+ innerConeAngle(0.2f)
286
+ outerConeAngle(0.5f)
287
+ },
288
+ position = Position(-2f, 3f, 1f)
289
+ )
290
+
291
+ modelInstance?.let { instance ->
292
+ ModelNode(
293
+ modelInstance = instance,
294
+ scaleToUnits = 2.0f,
295
+ centerOrigin = Position(0f, 0f, 0f)
296
+ )
297
+ }
298
+ }
299
+ }`,
300
+ notes: [
301
+ "Night scenes use POINT and SPOT lights instead of directional/sun.",
302
+ "Lower intensities (30K-50K lux) create mood lighting.",
303
+ "Use warm colors (orange/yellow) for lamps, cool colors (blue) for moonlight.",
304
+ "falloff controls how far the light reaches (in meters).",
305
+ "Spot lights need innerConeAngle and outerConeAngle (in radians).",
306
+ ],
307
+ },
308
+ "ar-lighting": {
309
+ description: "AR scene with environmental HDR light estimation",
310
+ code: `@Composable
311
+ fun ARLightingScreen() {
312
+ val engine = rememberEngine()
313
+ val modelLoader = rememberModelLoader(engine)
314
+ val modelInstance = rememberModelInstance(modelLoader, "models/furniture.glb")
315
+ var anchor by remember { mutableStateOf<Anchor?>(null) }
316
+
317
+ ARSceneView(
318
+ modifier = Modifier.fillMaxSize(),
319
+ engine = engine,
320
+ modelLoader = modelLoader,
321
+ planeRenderer = true,
322
+ sessionConfiguration = { session, config ->
323
+ // ENVIRONMENTAL_HDR is the most realistic lighting mode
324
+ // It captures real-world lighting and applies it to virtual objects
325
+ config.lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR
326
+ config.depthMode =
327
+ if (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC))
328
+ Config.DepthMode.AUTOMATIC else Config.DepthMode.DISABLED
329
+ config.planeFindingMode = Config.PlaneFindingMode.HORIZONTAL_AND_VERTICAL
330
+ },
331
+ onTouchEvent = { event, hitResult ->
332
+ if (event.action == MotionEvent.ACTION_UP && hitResult != null) {
333
+ anchor = hitResult.createAnchor()
334
+ }
335
+ true
336
+ }
337
+ ) {
338
+ anchor?.let { a ->
339
+ AnchorNode(anchor = a) {
340
+ modelInstance?.let { instance ->
341
+ ModelNode(
342
+ modelInstance = instance,
343
+ scaleToUnits = 0.5f,
344
+ isEditable = true
345
+ )
346
+ }
347
+ }
348
+ }
349
+ }
350
+ }`,
351
+ notes: [
352
+ "ENVIRONMENTAL_HDR captures the real environment and applies it as IBL to virtual objects.",
353
+ "This is the most realistic AR lighting mode but costs more CPU/GPU.",
354
+ "For simpler lighting, use AMBIENT_INTENSITY (just brightness + color temperature).",
355
+ "ARCore automatically adjusts directional light to match real shadows.",
356
+ ],
357
+ },
358
+ };
359
+ const IOS_ENVIRONMENTS = {
360
+ "hdr-environment": {
361
+ description: "RealityKit scene with environment lighting in SwiftUI",
362
+ code: `import SwiftUI
363
+ import SceneViewSwift
364
+ import RealityKit
365
+
366
+ struct EnvironmentLightingView: View {
367
+ @State private var model: ModelNode?
368
+
369
+ var body: some View {
370
+ SceneView { root in
371
+ if let model {
372
+ root.addChild(model.entity)
373
+ }
374
+ // RealityKit uses built-in IBL automatically
375
+ // For custom environments, use .environment(.lighting) modifiers
376
+ }
377
+ .cameraControls(.orbit)
378
+ .task {
379
+ do {
380
+ model = try await ModelNode.load("models/helmet.usdz")
381
+ model?.scaleToUnits(1.0)
382
+ } catch {
383
+ print("Failed to load: \\(error)")
384
+ }
385
+ }
386
+ }
387
+ }`,
388
+ notes: [
389
+ "RealityKit provides built-in IBL automatically.",
390
+ "For custom environment maps, use ImageBasedLightComponent on the anchor entity.",
391
+ "USDZ models carry their own material properties that work with RealityKit's PBR pipeline.",
392
+ ],
393
+ },
394
+ "ar-lighting": {
395
+ description: "iOS AR with automatic environment lighting from ARKit",
396
+ code: `import SwiftUI
397
+ import SceneViewSwift
398
+ import RealityKit
399
+
400
+ struct ARLightingView: View {
401
+ @State private var model: ModelNode?
402
+
403
+ var body: some View {
404
+ ARSceneView(
405
+ planeDetection: .horizontal,
406
+ showCoachingOverlay: true,
407
+ onTapOnPlane: { position, arView in
408
+ guard let model else { return }
409
+ let anchor = AnchorNode.world(position: position)
410
+ let clone = model.entity.clone(recursive: true)
411
+ clone.scale = .init(repeating: 0.5)
412
+ anchor.add(clone)
413
+ arView.scene.addAnchor(anchor.entity)
414
+ }
415
+ )
416
+ .edgesIgnoringSafeArea(.all)
417
+ .task {
418
+ do {
419
+ model = try await ModelNode.load("models/furniture.usdz")
420
+ } catch {
421
+ print("Failed to load: \\(error)")
422
+ }
423
+ }
424
+ }
425
+ }`,
426
+ notes: [
427
+ "ARKit automatically provides environment lighting estimation.",
428
+ "RealityKit applies real-world lighting to virtual objects by default.",
429
+ "No manual light estimation configuration needed on iOS.",
430
+ ],
431
+ },
432
+ };
433
+ export function generateEnvironmentCode(environmentType, platform = "android") {
434
+ if (platform === "ios") {
435
+ const iosEnv = IOS_ENVIRONMENTS[environmentType];
436
+ if (iosEnv) {
437
+ return {
438
+ code: iosEnv.code,
439
+ platform: "ios",
440
+ environmentType,
441
+ description: iosEnv.description,
442
+ notes: iosEnv.notes,
443
+ };
444
+ }
445
+ return {
446
+ code: IOS_ENVIRONMENTS["hdr-environment"].code,
447
+ platform: "ios",
448
+ environmentType: "hdr-environment",
449
+ description: `iOS equivalent for '${environmentType}' not available. Showing HDR environment instead.`,
450
+ notes: [`The '${environmentType}' environment type is Android-specific. RealityKit handles lighting differently.`],
451
+ };
452
+ }
453
+ const env = ANDROID_ENVIRONMENTS[environmentType];
454
+ if (!env)
455
+ return null;
456
+ return {
457
+ code: env.code,
458
+ platform: "android",
459
+ environmentType,
460
+ description: env.description,
461
+ notes: env.notes,
462
+ };
463
+ }
464
+ export function formatEnvironmentCode(result) {
465
+ const lang = result.platform === "ios" ? "swift" : "kotlin";
466
+ const parts = [
467
+ `## Environment: ${result.environmentType}`,
468
+ `**Platform:** ${result.platform === "ios" ? "iOS (SwiftUI + RealityKit)" : "Android (Jetpack Compose + Filament)"}`,
469
+ `**Description:** ${result.description}`,
470
+ ``,
471
+ `### Code`,
472
+ ``,
473
+ "```" + lang,
474
+ result.code,
475
+ "```",
476
+ ``,
477
+ ];
478
+ if (result.notes.length > 0) {
479
+ parts.push(`### Notes`);
480
+ result.notes.forEach((n, i) => parts.push(`${i + 1}. ${n}`));
481
+ }
482
+ return parts.join("\n");
483
+ }