sceneview-mcp 3.4.7 → 3.4.8

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Thomas Gorisse
3
+ Copyright (c) 2024-2026 Thomas Gorisse
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -3,9 +3,11 @@
3
3
  [![npm version](https://img.shields.io/npm/v/sceneview-mcp?color=6c35aa)](https://www.npmjs.com/package/sceneview-mcp)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/sceneview-mcp?color=blue)](https://www.npmjs.com/package/sceneview-mcp)
5
5
  [![MCP](https://img.shields.io/badge/MCP-v1.12-blue)](https://modelcontextprotocol.io/)
6
- [![License](https://img.shields.io/badge/License-Apache%202.0-green)](https://www.apache.org/licenses/LICENSE-2.0)
6
+ [![License](https://img.shields.io/badge/License-MIT-green)](./LICENSE)
7
7
  [![Node](https://img.shields.io/badge/Node-%3E%3D18-brightgreen)](https://nodejs.org/)
8
8
 
9
+ > **Disclaimer:** This tool generates code suggestions for the SceneView SDK. Generated code is provided "as is" without warranty. Always review generated code before use in production. This is not a substitute for professional software engineering review. See [TERMS.md](./TERMS.md) and [PRIVACY.md](./PRIVACY.md).
10
+
9
11
  The official [Model Context Protocol](https://modelcontextprotocol.io/) server for **SceneView** — giving AI assistants deep knowledge of the SceneView 3D/AR SDK so they generate correct, compilable Kotlin code.
10
12
 
11
13
  ---
@@ -265,6 +267,12 @@ Published to npm on each SceneView release:
265
267
  npm publish --access public
266
268
  ```
267
269
 
270
+ ## Legal
271
+
272
+ - [LICENSE](./LICENSE) — MIT License
273
+ - [TERMS.md](./TERMS.md) — Terms of Service
274
+ - [PRIVACY.md](./PRIVACY.md) — Privacy Policy (no data collected)
275
+
268
276
  ## License
269
277
 
270
- Apache 2.0 same as SceneView.
278
+ MITsee [LICENSE](./LICENSE).
package/dist/index.js CHANGED
@@ -13,6 +13,17 @@ import { parseNodeSections, findNodeSection, listNodeTypes } from "./node-refere
13
13
  import { PLATFORM_ROADMAP, BEST_PRACTICES, AR_SETUP_GUIDE, TROUBLESHOOTING_GUIDE } from "./guides.js";
14
14
  import { buildPreviewUrl, validatePreviewInput, formatPreviewResponse } from "./preview.js";
15
15
  const __dirname = dirname(fileURLToPath(import.meta.url));
16
+ // ─── Legal disclaimer ─────────────────────────────────────────────────────────
17
+ const DISCLAIMER = '\n\n---\n*Generated code suggestion. Review before use in production. See [TERMS.md](https://github.com/SceneView/sceneview/blob/main/mcp/TERMS.md).*';
18
+ function withDisclaimer(content) {
19
+ if (content.length === 0)
20
+ return content;
21
+ const last = content[content.length - 1];
22
+ return [
23
+ ...content.slice(0, -1),
24
+ { ...last, text: last.text + DISCLAIMER },
25
+ ];
26
+ }
16
27
  let API_DOCS;
17
28
  try {
18
29
  API_DOCS = readFileSync(resolve(__dirname, "../llms.txt"), "utf-8");
@@ -271,7 +282,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
271
282
  const codeLang = isIos ? "swift" : "kotlin";
272
283
  const codeLabel = isIos ? "**Swift (SwiftUI):**" : "**Kotlin (Jetpack Compose):**";
273
284
  return {
274
- content: [
285
+ content: withDisclaimer([
275
286
  {
276
287
  type: "text",
277
288
  text: [
@@ -290,7 +301,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
290
301
  `> ${sample.prompt}`,
291
302
  ].join("\n"),
292
303
  },
293
- ],
304
+ ]),
294
305
  };
295
306
  }
296
307
  // ── list_samples ──────────────────────────────────────────────────────────
@@ -316,14 +327,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
316
327
  return `### \`${s.id}\`\n**${s.title}**${s.language === "swift" ? " (Swift/iOS)" : ""}\n${s.description}\n*Tags:* ${s.tags.join(", ")}\n${depLabel} \`${s.dependency}\`\n\nCall \`get_sample("${s.id}")\` for the full code.`;
317
328
  })
318
329
  .join("\n\n---\n\n");
319
- return { content: [{ type: "text", text: header + rows }] };
330
+ return { content: withDisclaimer([{ type: "text", text: header + rows }]) };
320
331
  }
321
332
  // ── get_setup ─────────────────────────────────────────────────────────────
322
333
  case "get_setup": {
323
334
  const type = request.params.arguments?.type;
324
335
  if (type === "3d") {
325
336
  return {
326
- content: [
337
+ content: withDisclaimer([
327
338
  {
328
339
  type: "text",
329
340
  text: [
@@ -339,12 +350,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
339
350
  `No manifest changes required for 3D-only scenes.`,
340
351
  ].join("\n"),
341
352
  },
342
- ],
353
+ ]),
343
354
  };
344
355
  }
345
356
  if (type === "ar") {
346
357
  return {
347
- content: [
358
+ content: withDisclaimer([
348
359
  {
349
360
  type: "text",
350
361
  text: [
@@ -367,7 +378,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
367
378
  `\`\`\``,
368
379
  ].join("\n"),
369
380
  },
370
- ],
381
+ ]),
371
382
  };
372
383
  }
373
384
  return {
@@ -386,11 +397,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
386
397
  }
387
398
  const issues = validateCode(code);
388
399
  const report = formatValidationReport(issues);
389
- return { content: [{ type: "text", text: report }] };
400
+ return { content: withDisclaimer([{ type: "text", text: report }]) };
390
401
  }
391
402
  // ── get_migration_guide ───────────────────────────────────────────────────
392
403
  case "get_migration_guide": {
393
- return { content: [{ type: "text", text: MIGRATION_GUIDE }] };
404
+ return { content: withDisclaimer([{ type: "text", text: MIGRATION_GUIDE }]) };
394
405
  }
395
406
  // ── get_node_reference ────────────────────────────────────────────────────
396
407
  case "get_node_reference": {
@@ -419,7 +430,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
419
430
  };
420
431
  }
421
432
  return {
422
- content: [
433
+ content: withDisclaimer([
423
434
  {
424
435
  type: "text",
425
436
  text: [
@@ -428,33 +439,33 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
428
439
  section.content,
429
440
  ].join("\n"),
430
441
  },
431
- ],
442
+ ]),
432
443
  };
433
444
  }
434
445
  // ── get_platform_roadmap ────────────────────────────────────────────────
435
446
  case "get_platform_roadmap": {
436
- return { content: [{ type: "text", text: PLATFORM_ROADMAP }] };
447
+ return { content: withDisclaimer([{ type: "text", text: PLATFORM_ROADMAP }]) };
437
448
  }
438
449
  // ── get_best_practices ───────────────────────────────────────────────────
439
450
  case "get_best_practices": {
440
451
  const category = request.params.arguments?.category || "all";
441
452
  const text = BEST_PRACTICES[category] ?? BEST_PRACTICES["all"];
442
- return { content: [{ type: "text", text }] };
453
+ return { content: withDisclaimer([{ type: "text", text }]) };
443
454
  }
444
455
  // ── get_ar_setup ─────────────────────────────────────────────────────────
445
456
  case "get_ar_setup": {
446
- return { content: [{ type: "text", text: AR_SETUP_GUIDE }] };
457
+ return { content: withDisclaimer([{ type: "text", text: AR_SETUP_GUIDE }]) };
447
458
  }
448
459
  // ── get_troubleshooting ──────────────────────────────────────────────────
449
460
  case "get_troubleshooting": {
450
- return { content: [{ type: "text", text: TROUBLESHOOTING_GUIDE }] };
461
+ return { content: withDisclaimer([{ type: "text", text: TROUBLESHOOTING_GUIDE }]) };
451
462
  }
452
463
  // ── get_ios_setup ─────────────────────────────────────────────────────────
453
464
  case "get_ios_setup": {
454
465
  const iosType = request.params.arguments?.type;
455
466
  if (iosType === "3d") {
456
467
  return {
457
- content: [
468
+ content: withDisclaimer([
458
469
  {
459
470
  type: "text",
460
471
  text: [
@@ -534,12 +545,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
534
545
  `No manifest or permission changes needed for 3D-only scenes.`,
535
546
  ].join("\n"),
536
547
  },
537
- ],
548
+ ]),
538
549
  };
539
550
  }
540
551
  if (iosType === "ar") {
541
552
  return {
542
- content: [
553
+ content: withDisclaimer([
543
554
  {
544
555
  type: "text",
545
556
  text: [
@@ -625,7 +636,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
625
636
  `\`\`\``,
626
637
  ].join("\n"),
627
638
  },
628
- ],
639
+ ]),
629
640
  };
630
641
  }
631
642
  return {
@@ -636,7 +647,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
636
647
  // ── get_web_setup ────────────────────────────────────────────────────────
637
648
  case "get_web_setup": {
638
649
  return {
639
- content: [
650
+ content: withDisclaimer([
640
651
  {
641
652
  type: "text",
642
653
  text: [
@@ -721,7 +732,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
721
732
  `- glTF/GLB format only (same as Android)`,
722
733
  ].join("\n"),
723
734
  },
724
- ],
735
+ ]),
725
736
  };
726
737
  }
727
738
  // ── render_3d_preview ──────────────────────────────────────────────────
@@ -740,7 +751,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
740
751
  }
741
752
  const result = buildPreviewUrl({ modelUrl, codeSnippet, autoRotate, ar, title });
742
753
  const text = formatPreviewResponse(result);
743
- return { content: [{ type: "text", text }] };
754
+ return { content: withDisclaimer([{ type: "text", text }]) };
744
755
  }
745
756
  default:
746
757
  return {
package/dist/samples.js CHANGED
@@ -181,6 +181,50 @@ fun PointCloudScreen() {
181
181
  ) {
182
182
  // Render point cloud model instances at detected positions
183
183
  }
184
+ }`,
185
+ },
186
+ "ar-face-mesh": {
187
+ id: "ar-face-mesh",
188
+ title: "AR Face Mesh",
189
+ description: "AR face tracking with AugmentedFaceNode — applies a textured mesh overlay to detected faces using the front camera.",
190
+ tags: ["ar", "face-tracking", "model"],
191
+ dependency: "io.github.sceneview:arsceneview:3.3.0",
192
+ prompt: "Create an AR screen that uses the front camera to detect faces and overlay a 3D mesh on them. Use SceneView `io.github.sceneview:arsceneview:3.3.0`.",
193
+ code: `@Composable
194
+ fun ARFaceMeshScreen() {
195
+ val engine = rememberEngine()
196
+ val modelLoader = rememberModelLoader(engine)
197
+ val materialLoader = rememberMaterialLoader(engine)
198
+ var trackedFaces by remember { mutableStateOf(listOf<AugmentedFace>()) }
199
+
200
+ val faceMaterial = remember(materialLoader) {
201
+ materialLoader.createColorInstance(
202
+ color = Color(0.8f, 0.6f, 0.4f, 0.5f),
203
+ metallic = 0f,
204
+ roughness = 0.9f
205
+ )
206
+ }
207
+
208
+ ARScene(
209
+ modifier = Modifier.fillMaxSize(),
210
+ engine = engine,
211
+ modelLoader = modelLoader,
212
+ sessionFeatures = setOf(Session.Feature.FRONT_CAMERA),
213
+ sessionConfiguration = { _, config ->
214
+ config.augmentedFaceMode = Config.AugmentedFaceMode.MESH3D
215
+ },
216
+ onSessionUpdated = { session, _ ->
217
+ trackedFaces = session.getAllTrackables(AugmentedFace::class.java)
218
+ .filter { it.trackingState == TrackingState.TRACKING }
219
+ }
220
+ ) {
221
+ trackedFaces.forEach { face ->
222
+ AugmentedFaceNode(
223
+ augmentedFace = face,
224
+ meshMaterialInstance = faceMaterial
225
+ )
226
+ }
227
+ }
184
228
  }`,
185
229
  },
186
230
  "gltf-camera": {
@@ -242,6 +286,67 @@ fun CameraManipulatorScreen() {
242
286
  ModelNode(modelInstance = instance, scaleToUnits = 1.0f)
243
287
  }
244
288
  }
289
+ }`,
290
+ },
291
+ "camera-animation": {
292
+ id: "camera-animation",
293
+ title: "Camera Animation",
294
+ description: "Animated camera flythrough around a 3D model — smooth orbit using LaunchedEffect and trigonometric interpolation.",
295
+ tags: ["3d", "camera", "animation", "model"],
296
+ dependency: "io.github.sceneview:sceneview:3.3.0",
297
+ prompt: "Create a 3D scene with a camera that automatically orbits around a model in a smooth circle. Include a play/pause button. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
298
+ code: `@Composable
299
+ fun CameraAnimationScreen() {
300
+ val engine = rememberEngine()
301
+ val modelLoader = rememberModelLoader(engine)
302
+ val environmentLoader = rememberEnvironmentLoader(engine)
303
+ var isOrbiting by remember { mutableStateOf(true) }
304
+ var angle by remember { mutableFloatStateOf(0f) }
305
+
306
+ val cameraNode = rememberCameraNode(engine) {
307
+ position = Position(x = 0f, y = 1.5f, z = 4f)
308
+ lookAt(Position(0f, 0f, 0f))
309
+ }
310
+
311
+ // Animate camera orbit
312
+ LaunchedEffect(isOrbiting) {
313
+ while (isOrbiting) {
314
+ withFrameNanos { _ ->
315
+ angle += 0.5f
316
+ val radians = Math.toRadians(angle.toDouble())
317
+ cameraNode.position = Position(
318
+ x = (4f * sin(radians)).toFloat(),
319
+ y = 1.5f,
320
+ z = (4f * cos(radians)).toFloat()
321
+ )
322
+ cameraNode.lookAt(Position(0f, 0f, 0f))
323
+ }
324
+ }
325
+ }
326
+
327
+ Column {
328
+ Scene(
329
+ modifier = Modifier.weight(1f).fillMaxWidth(),
330
+ engine = engine,
331
+ modelLoader = modelLoader,
332
+ cameraNode = cameraNode,
333
+ environment = rememberEnvironment(environmentLoader) {
334
+ environmentLoader.createHDREnvironment("environments/sky_2k.hdr")
335
+ ?: createEnvironment(environmentLoader)
336
+ },
337
+ mainLightNode = rememberMainLightNode(engine) { intensity = 100_000f }
338
+ ) {
339
+ rememberModelInstance(modelLoader, "models/damaged_helmet.glb")?.let { instance ->
340
+ ModelNode(modelInstance = instance, scaleToUnits = 1.0f)
341
+ }
342
+ }
343
+ Button(
344
+ onClick = { isOrbiting = !isOrbiting },
345
+ modifier = Modifier.align(Alignment.CenterHorizontally).padding(16.dp)
346
+ ) {
347
+ Text(if (isOrbiting) "Stop Orbit" else "Start Orbit")
348
+ }
349
+ }
245
350
  }`,
246
351
  },
247
352
  "autopilot-demo": {
@@ -466,6 +571,465 @@ fun PostProcessingScreen() {
466
571
  }
467
572
  // Configure view.bloomOptions, view.vignetteOptions, etc.
468
573
  // See samples/post-processing for full interactive controls
574
+ }`,
575
+ },
576
+ "video-texture": {
577
+ id: "video-texture",
578
+ title: "Video Texture",
579
+ description: "Video playback on a 3D plane using VideoNode with MediaPlayer — supports looping, chroma-key, and auto-sizing.",
580
+ tags: ["3d", "video", "model"],
581
+ dependency: "io.github.sceneview:sceneview:3.3.0",
582
+ prompt: "Create a 3D scene with a video playing on a floating 3D plane. Include play/pause controls and chroma-key support. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
583
+ code: `@Composable
584
+ fun VideoTextureScreen() {
585
+ val context = LocalContext.current
586
+ val engine = rememberEngine()
587
+ var isPlaying by remember { mutableStateOf(true) }
588
+
589
+ val player = remember {
590
+ MediaPlayer().apply {
591
+ setDataSource(context, Uri.parse("android.resource://\${context.packageName}/raw/video"))
592
+ isLooping = true
593
+ prepare()
594
+ start()
595
+ }
596
+ }
597
+ DisposableEffect(Unit) { onDispose { player.release() } }
598
+
599
+ Column {
600
+ Scene(
601
+ modifier = Modifier.weight(1f).fillMaxWidth(),
602
+ engine = engine
603
+ ) {
604
+ VideoNode(
605
+ player = player,
606
+ // size = null auto-sizes from video aspect ratio (longer edge = 1 unit)
607
+ position = Position(z = -2f),
608
+ chromaKeyColor = null // set to android.graphics.Color.GREEN for green-screen
609
+ )
610
+ }
611
+ Row(
612
+ modifier = Modifier.fillMaxWidth().padding(16.dp),
613
+ horizontalArrangement = Arrangement.Center,
614
+ verticalAlignment = Alignment.CenterVertically
615
+ ) {
616
+ Button(onClick = {
617
+ if (isPlaying) player.pause() else player.start()
618
+ isPlaying = !isPlaying
619
+ }) {
620
+ Text(if (isPlaying) "Pause" else "Play")
621
+ }
622
+ }
623
+ }
624
+ }`,
625
+ },
626
+ "multi-model-scene": {
627
+ id: "multi-model-scene",
628
+ title: "Multi-Model Scene",
629
+ description: "Scene with multiple 3D models loaded independently, positioned and scaled to create a complete environment.",
630
+ tags: ["3d", "model", "multi-model", "environment"],
631
+ dependency: "io.github.sceneview:sceneview:3.3.0",
632
+ prompt: "Create a 3D scene that loads multiple GLB models (a car, a building, and trees) and positions them to form a street scene. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
633
+ code: `@Composable
634
+ fun MultiModelScreen() {
635
+ val engine = rememberEngine()
636
+ val modelLoader = rememberModelLoader(engine)
637
+ val materialLoader = rememberMaterialLoader(engine)
638
+ val environmentLoader = rememberEnvironmentLoader(engine)
639
+
640
+ Scene(
641
+ modifier = Modifier.fillMaxSize(),
642
+ engine = engine,
643
+ modelLoader = modelLoader,
644
+ cameraManipulator = rememberCameraManipulator(
645
+ orbitHomePosition = Position(x = 0f, y = 3f, z = 8f),
646
+ targetPosition = Position(0f, 0f, 0f)
647
+ ),
648
+ environment = rememberEnvironment(environmentLoader) {
649
+ environmentLoader.createHDREnvironment("environments/sky_2k.hdr")
650
+ ?: createEnvironment(environmentLoader)
651
+ },
652
+ mainLightNode = rememberMainLightNode(engine) { intensity = 100_000f }
653
+ ) {
654
+ // Ground plane
655
+ val groundMat = remember(materialLoader) {
656
+ materialLoader.createColorInstance(Color.DarkGray, roughness = 0.9f)
657
+ }
658
+ PlaneNode(size = Size(20f, 20f), materialInstance = groundMat)
659
+
660
+ // Car in the center
661
+ rememberModelInstance(modelLoader, "models/car.glb")?.let { car ->
662
+ ModelNode(
663
+ modelInstance = car,
664
+ scaleToUnits = 2.0f,
665
+ position = Position(x = 0f, y = 0f, z = 0f),
666
+ autoAnimate = true
667
+ )
668
+ }
669
+
670
+ // Building on the left
671
+ rememberModelInstance(modelLoader, "models/building.glb")?.let { building ->
672
+ ModelNode(
673
+ modelInstance = building,
674
+ scaleToUnits = 5.0f,
675
+ position = Position(x = -6f, y = 0f, z = -3f)
676
+ )
677
+ }
678
+
679
+ // Trees along the right side
680
+ for (i in 0..2) {
681
+ rememberModelInstance(modelLoader, "models/tree.glb")?.let { tree ->
682
+ ModelNode(
683
+ modelInstance = tree,
684
+ scaleToUnits = 3.0f,
685
+ position = Position(x = 5f, y = 0f, z = i * -3f)
686
+ )
687
+ }
688
+ }
689
+ }
690
+ }`,
691
+ },
692
+ "gesture-interaction": {
693
+ id: "gesture-interaction",
694
+ title: "Gesture Interaction",
695
+ description: "Full gesture handling — tap to select, double-tap to scale, long-press for info, pinch-to-scale, drag-to-move on editable nodes.",
696
+ tags: ["3d", "gestures", "model"],
697
+ dependency: "io.github.sceneview:sceneview:3.3.0",
698
+ prompt: "Create a 3D scene with a model that responds to tap (select), double-tap (scale up), long-press (show info), and supports pinch-to-scale and drag-to-move. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
699
+ code: `@Composable
700
+ fun GestureInteractionScreen() {
701
+ val engine = rememberEngine()
702
+ val modelLoader = rememberModelLoader(engine)
703
+ val environmentLoader = rememberEnvironmentLoader(engine)
704
+ var selectedNode by remember { mutableStateOf<String?>(null) }
705
+ var infoText by remember { mutableStateOf("Tap a model to select it") }
706
+
707
+ Box(modifier = Modifier.fillMaxSize()) {
708
+ Scene(
709
+ modifier = Modifier.fillMaxSize(),
710
+ engine = engine,
711
+ modelLoader = modelLoader,
712
+ cameraManipulator = rememberCameraManipulator(),
713
+ environment = rememberEnvironment(environmentLoader) {
714
+ environmentLoader.createHDREnvironment("environments/sky_2k.hdr")
715
+ ?: createEnvironment(environmentLoader)
716
+ },
717
+ onGestureListener = rememberOnGestureListener(
718
+ onSingleTapConfirmed = { event, node ->
719
+ selectedNode = node?.name
720
+ infoText = if (node != null) "Selected: \${node.name}" else "Tap a model to select it"
721
+ },
722
+ onDoubleTap = { event, node ->
723
+ node?.let {
724
+ it.scale = if (it.scale.x > 1.5f) Scale(1f) else Scale(2f)
725
+ infoText = "Double-tap: toggled scale"
726
+ }
727
+ },
728
+ onLongPress = { event, node ->
729
+ node?.let {
730
+ infoText = "Position: \${it.worldPosition}, Scale: \${it.scale}"
731
+ }
732
+ }
733
+ )
734
+ ) {
735
+ rememberModelInstance(modelLoader, "models/damaged_helmet.glb")?.let { instance ->
736
+ ModelNode(
737
+ modelInstance = instance,
738
+ scaleToUnits = 1.0f,
739
+ isEditable = true, // enables pinch-to-scale and drag-to-move
740
+ autoAnimate = true
741
+ )
742
+ }
743
+ }
744
+
745
+ // Info overlay
746
+ Text(
747
+ text = infoText,
748
+ modifier = Modifier.align(Alignment.TopCenter).padding(24.dp)
749
+ .background(MaterialTheme.colorScheme.surface.copy(alpha = 0.8f), RoundedCornerShape(8.dp))
750
+ .padding(12.dp),
751
+ style = MaterialTheme.typography.bodyMedium
752
+ )
753
+ }
754
+ }`,
755
+ },
756
+ "environment-lighting": {
757
+ id: "environment-lighting",
758
+ title: "Environment & Lighting",
759
+ description: "Complete lighting setup — HDR environment (IBL + skybox), main directional light, point light, and spot light with LightNode.",
760
+ tags: ["3d", "environment", "lighting", "model"],
761
+ dependency: "io.github.sceneview:sceneview:3.3.0",
762
+ prompt: "Create a 3D scene with full HDR environment lighting (IBL + skybox), a directional sun light, a red point light, and a blue spot light. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
763
+ code: `@Composable
764
+ fun EnvironmentLightingScreen() {
765
+ val engine = rememberEngine()
766
+ val modelLoader = rememberModelLoader(engine)
767
+ val materialLoader = rememberMaterialLoader(engine)
768
+ val environmentLoader = rememberEnvironmentLoader(engine)
769
+
770
+ Scene(
771
+ modifier = Modifier.fillMaxSize(),
772
+ engine = engine,
773
+ modelLoader = modelLoader,
774
+ cameraManipulator = rememberCameraManipulator(
775
+ orbitHomePosition = Position(x = 0f, y = 2f, z = 5f),
776
+ targetPosition = Position(0f, 0f, 0f)
777
+ ),
778
+ // HDR environment provides both IBL (indirect lighting) and skybox (background)
779
+ environment = rememberEnvironment(environmentLoader) {
780
+ environmentLoader.createHDREnvironment("environments/sky_2k.hdr")
781
+ ?: createEnvironment(environmentLoader)
782
+ },
783
+ // Main directional light (sun)
784
+ mainLightNode = rememberMainLightNode(engine) {
785
+ intensity = 100_000f
786
+ // castShadows is true by default for the main light
787
+ }
788
+ ) {
789
+ // Floor to receive shadows
790
+ val floorMat = remember(materialLoader) {
791
+ materialLoader.createColorInstance(Color.LightGray, roughness = 0.8f)
792
+ }
793
+ PlaneNode(size = Size(10f, 10f), materialInstance = floorMat)
794
+
795
+ // Model
796
+ rememberModelInstance(modelLoader, "models/damaged_helmet.glb")?.let { instance ->
797
+ ModelNode(modelInstance = instance, scaleToUnits = 1.0f, position = Position(y = 0.5f))
798
+ }
799
+
800
+ // Red point light on the left
801
+ LightNode(
802
+ type = LightManager.Type.POINT,
803
+ apply = {
804
+ color(1.0f, 0.2f, 0.2f)
805
+ intensity(200_000f)
806
+ falloff(5.0f)
807
+ },
808
+ position = Position(x = -2f, y = 2f, z = 1f)
809
+ )
810
+
811
+ // Blue spot light on the right
812
+ LightNode(
813
+ type = LightManager.Type.SPOT,
814
+ apply = {
815
+ color(0.2f, 0.4f, 1.0f)
816
+ intensity(300_000f)
817
+ falloff(8.0f)
818
+ castShadows(true)
819
+ },
820
+ position = Position(x = 2f, y = 3f, z = 1f)
821
+ )
822
+ }
823
+ }`,
824
+ },
825
+ "procedural-geometry": {
826
+ id: "procedural-geometry",
827
+ title: "Procedural Geometry",
828
+ description: "Procedural shapes — CubeNode, SphereNode, CylinderNode, PlaneNode — with PBR materials (metallic, roughness, color).",
829
+ tags: ["3d", "geometry", "model"],
830
+ dependency: "io.github.sceneview:sceneview:3.3.0",
831
+ prompt: "Create a 3D scene showing procedural geometry shapes (cube, sphere, cylinder, plane) with different PBR materials. No model files needed. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
832
+ code: `@Composable
833
+ fun ProceduralGeometryScreen() {
834
+ val engine = rememberEngine()
835
+ val materialLoader = rememberMaterialLoader(engine)
836
+ val environmentLoader = rememberEnvironmentLoader(engine)
837
+
838
+ Scene(
839
+ modifier = Modifier.fillMaxSize(),
840
+ engine = engine,
841
+ cameraManipulator = rememberCameraManipulator(
842
+ orbitHomePosition = Position(x = 0f, y = 2f, z = 6f),
843
+ targetPosition = Position(0f, 0.5f, 0f)
844
+ ),
845
+ environment = rememberEnvironment(environmentLoader) {
846
+ environmentLoader.createHDREnvironment("environments/sky_2k.hdr")
847
+ ?: createEnvironment(environmentLoader)
848
+ },
849
+ mainLightNode = rememberMainLightNode(engine) { intensity = 100_000f }
850
+ ) {
851
+ // Floor
852
+ val floorMat = remember(materialLoader) {
853
+ materialLoader.createColorInstance(Color.DarkGray, roughness = 0.9f)
854
+ }
855
+ PlaneNode(size = Size(8f, 8f), materialInstance = floorMat)
856
+
857
+ // Red matte cube
858
+ val redMat = remember(materialLoader) {
859
+ materialLoader.createColorInstance(Color.Red, metallic = 0f, roughness = 0.6f)
860
+ }
861
+ CubeNode(
862
+ size = Size(0.6f),
863
+ center = Position(0f, 0.3f, 0f),
864
+ materialInstance = redMat,
865
+ position = Position(x = -2f)
866
+ )
867
+
868
+ // Chrome sphere
869
+ val chromeMat = remember(materialLoader) {
870
+ materialLoader.createColorInstance(Color.Gray, metallic = 1f, roughness = 0.05f, reflectance = 0.9f)
871
+ }
872
+ SphereNode(
873
+ radius = 0.4f,
874
+ materialInstance = chromeMat,
875
+ position = Position(x = -0.7f, y = 0.4f)
876
+ )
877
+
878
+ // Green cylinder
879
+ val greenMat = remember(materialLoader) {
880
+ materialLoader.createColorInstance(Color.Green, metallic = 0.2f, roughness = 0.4f)
881
+ }
882
+ CylinderNode(
883
+ radius = 0.25f,
884
+ height = 0.8f,
885
+ materialInstance = greenMat,
886
+ position = Position(x = 0.7f, y = 0.4f)
887
+ )
888
+
889
+ // Gold sphere
890
+ val goldMat = remember(materialLoader) {
891
+ materialLoader.createColorInstance(
892
+ Color(1f, 0.84f, 0f),
893
+ metallic = 1f,
894
+ roughness = 0.3f
895
+ )
896
+ }
897
+ SphereNode(
898
+ radius = 0.35f,
899
+ materialInstance = goldMat,
900
+ position = Position(x = 2f, y = 0.35f)
901
+ )
902
+ }
903
+ }`,
904
+ },
905
+ "compose-ui-3d": {
906
+ id: "compose-ui-3d",
907
+ title: "Compose UI in 3D",
908
+ description: "Embed interactive Jetpack Compose UI (Cards, Buttons, Text) inside 3D space using ViewNode.",
909
+ tags: ["3d", "compose-ui", "text"],
910
+ dependency: "io.github.sceneview:sceneview:3.3.0",
911
+ prompt: "Create a 3D scene with interactive Compose UI elements (Card with text and a button) floating in 3D space using ViewNode. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
912
+ code: `@Composable
913
+ fun ComposeUI3DScreen() {
914
+ val engine = rememberEngine()
915
+ val modelLoader = rememberModelLoader(engine)
916
+ val windowManager = rememberViewNodeManager()
917
+ var clickCount by remember { mutableIntStateOf(0) }
918
+
919
+ Scene(
920
+ modifier = Modifier.fillMaxSize(),
921
+ engine = engine,
922
+ modelLoader = modelLoader,
923
+ cameraManipulator = rememberCameraManipulator(),
924
+ viewNodeWindowManager = windowManager
925
+ ) {
926
+ // 3D model behind the UI
927
+ rememberModelInstance(modelLoader, "models/damaged_helmet.glb")?.let { instance ->
928
+ ModelNode(modelInstance = instance, scaleToUnits = 1.0f, position = Position(z = -1f))
929
+ }
930
+
931
+ // Floating Compose Card in 3D space
932
+ ViewNode(
933
+ windowManager = windowManager,
934
+ position = Position(x = 0f, y = 1.2f, z = 0.5f)
935
+ ) {
936
+ Card(
937
+ modifier = Modifier.width(200.dp).padding(8.dp),
938
+ colors = CardDefaults.cardColors(
939
+ containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f)
940
+ )
941
+ ) {
942
+ Column(modifier = Modifier.padding(16.dp)) {
943
+ Text("Hello 3D World!", style = MaterialTheme.typography.titleMedium)
944
+ Text("Clicks: \$clickCount", style = MaterialTheme.typography.bodySmall)
945
+ Spacer(Modifier.height(8.dp))
946
+ Button(onClick = { clickCount++ }) {
947
+ Text("Click Me")
948
+ }
949
+ }
950
+ }
951
+ }
952
+ }
953
+ }`,
954
+ },
955
+ "node-hierarchy": {
956
+ id: "node-hierarchy",
957
+ title: "Node Hierarchy",
958
+ description: "Parent-child node relationships — a spinning solar system with planet groups orbiting a central sun.",
959
+ tags: ["3d", "hierarchy", "geometry", "animation"],
960
+ dependency: "io.github.sceneview:sceneview:3.3.0",
961
+ prompt: "Create a 3D solar system where planets orbit a sun using parent-child node hierarchies. Each planet group rotates independently. Use SceneView `io.github.sceneview:sceneview:3.3.0`.",
962
+ code: `@Composable
963
+ fun NodeHierarchyScreen() {
964
+ val engine = rememberEngine()
965
+ val materialLoader = rememberMaterialLoader(engine)
966
+ val environmentLoader = rememberEnvironmentLoader(engine)
967
+ var earthAngle by remember { mutableFloatStateOf(0f) }
968
+ var marsAngle by remember { mutableFloatStateOf(0f) }
969
+
970
+ // Animate planet orbits
971
+ LaunchedEffect(Unit) {
972
+ while (true) {
973
+ withFrameNanos { _ ->
974
+ earthAngle += 0.3f
975
+ marsAngle += 0.18f
976
+ }
977
+ }
978
+ }
979
+
980
+ Scene(
981
+ modifier = Modifier.fillMaxSize(),
982
+ engine = engine,
983
+ cameraManipulator = rememberCameraManipulator(
984
+ orbitHomePosition = Position(x = 0f, y = 4f, z = 8f),
985
+ targetPosition = Position(0f, 0f, 0f)
986
+ ),
987
+ environment = rememberEnvironment(environmentLoader) {
988
+ environmentLoader.createHDREnvironment("environments/sky_2k.hdr")
989
+ ?: createEnvironment(environmentLoader)
990
+ }
991
+ ) {
992
+ // Sun (center)
993
+ val sunMat = remember(materialLoader) {
994
+ materialLoader.createColorInstance(Color.Yellow, metallic = 0f, roughness = 1f)
995
+ }
996
+ SphereNode(radius = 0.5f, materialInstance = sunMat)
997
+
998
+ // Earth orbit group — parent node rotates, child offset creates orbit
999
+ Node(rotation = Rotation(y = earthAngle)) {
1000
+ // Earth sphere
1001
+ val earthMat = remember(materialLoader) {
1002
+ materialLoader.createColorInstance(Color.Blue, metallic = 0f, roughness = 0.7f)
1003
+ }
1004
+ SphereNode(radius = 0.2f, materialInstance = earthMat, position = Position(x = 2.5f))
1005
+
1006
+ // Moon orbits Earth (nested hierarchy)
1007
+ Node(position = Position(x = 2.5f), rotation = Rotation(y = earthAngle * 3f)) {
1008
+ val moonMat = remember(materialLoader) {
1009
+ materialLoader.createColorInstance(Color.LightGray, metallic = 0f, roughness = 0.9f)
1010
+ }
1011
+ SphereNode(radius = 0.06f, materialInstance = moonMat, position = Position(x = 0.4f))
1012
+ }
1013
+ }
1014
+
1015
+ // Mars orbit group
1016
+ Node(rotation = Rotation(y = marsAngle)) {
1017
+ val marsMat = remember(materialLoader) {
1018
+ materialLoader.createColorInstance(Color.Red, metallic = 0f, roughness = 0.8f)
1019
+ }
1020
+ SphereNode(radius = 0.15f, materialInstance = marsMat, position = Position(x = 4f))
1021
+ }
1022
+
1023
+ // Sun light
1024
+ LightNode(
1025
+ type = LightManager.Type.POINT,
1026
+ apply = {
1027
+ color(1.0f, 0.95f, 0.8f)
1028
+ intensity(500_000f)
1029
+ falloff(15.0f)
1030
+ }
1031
+ )
1032
+ }
469
1033
  }`,
470
1034
  },
471
1035
  // ─── iOS Samples ────────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sceneview-mcp",
3
- "version": "3.4.7",
3
+ "version": "3.4.8",
4
4
  "mcpName": "io.github.sceneview/mcp",
5
5
  "description": "MCP server for SceneView — cross-platform 3D & AR SDK for Android and iOS. Give Claude the full SceneView SDK so it writes correct, compilable code.",
6
6
  "keywords": [