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.
- package/README.md +39 -0
- package/dist/analyze-project.js +500 -0
- package/dist/auth.js +84 -0
- package/dist/billing.js +137 -0
- package/dist/convert-platform.js +302 -0
- package/dist/debug-issue.js +2 -2
- package/dist/explain-api.js +245 -0
- package/dist/extra-guides.js +1 -1
- package/dist/generate-animation.js +576 -0
- package/dist/generate-environment.js +483 -0
- package/dist/generate-gesture.js +532 -0
- package/dist/generate-physics.js +570 -0
- package/dist/generate-scene.js +4 -4
- package/dist/generated/llms-txt.js +6 -0
- package/dist/guides.js +8 -8
- package/dist/index.js +54 -1111
- package/dist/migration.js +2 -2
- package/dist/optimize-scene.js +173 -0
- package/dist/platform-setup.js +11 -11
- package/dist/samples.js +64 -64
- package/dist/search-models.js +214 -0
- package/dist/telemetry.js +120 -0
- package/dist/tiers.js +100 -0
- package/dist/tools/definitions.js +467 -0
- package/dist/tools/handler.js +791 -0
- package/dist/tools/index.js +18 -0
- package/dist/tools/types.js +8 -0
- package/dist/validator.js +1 -1
- package/llms.txt +24 -1
- package/package.json +7 -18
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* generate-physics.ts
|
|
3
|
+
*
|
|
4
|
+
* Generates compilable physics simulation code for SceneView.
|
|
5
|
+
*/
|
|
6
|
+
export const PHYSICS_TYPES = [
|
|
7
|
+
"gravity-drop",
|
|
8
|
+
"collision-detection",
|
|
9
|
+
"spring-physics",
|
|
10
|
+
"projectile",
|
|
11
|
+
"ragdoll-basic",
|
|
12
|
+
"rigid-body",
|
|
13
|
+
];
|
|
14
|
+
const PHYSICS_TEMPLATES = {
|
|
15
|
+
"gravity-drop": {
|
|
16
|
+
title: "Gravity Drop Simulation",
|
|
17
|
+
description: "Simulates objects falling under gravity using onFrame callback with velocity accumulation.",
|
|
18
|
+
android: `@Composable
|
|
19
|
+
fun GravityDropScene() {
|
|
20
|
+
val engine = rememberEngine()
|
|
21
|
+
val modelLoader = rememberModelLoader(engine)
|
|
22
|
+
val modelInstance = rememberModelInstance(modelLoader, "models/object.glb")
|
|
23
|
+
|
|
24
|
+
var posY by remember { mutableFloatStateOf(3.0f) }
|
|
25
|
+
var velocityY by remember { mutableFloatStateOf(0f) }
|
|
26
|
+
val gravity = -9.81f
|
|
27
|
+
val groundY = 0f
|
|
28
|
+
val bounceFactor = 0.6f
|
|
29
|
+
|
|
30
|
+
SceneView(
|
|
31
|
+
modifier = Modifier.fillMaxSize(),
|
|
32
|
+
engine = engine,
|
|
33
|
+
modelLoader = modelLoader,
|
|
34
|
+
onFrame = { frameTimeNanos ->
|
|
35
|
+
val dt = 1f / 60f // Fixed timestep for stability
|
|
36
|
+
velocityY += gravity * dt
|
|
37
|
+
posY += velocityY * dt
|
|
38
|
+
|
|
39
|
+
// Bounce off ground
|
|
40
|
+
if (posY <= groundY) {
|
|
41
|
+
posY = groundY
|
|
42
|
+
velocityY = -velocityY * bounceFactor
|
|
43
|
+
// Stop bouncing when velocity is negligible
|
|
44
|
+
if (kotlin.math.abs(velocityY) < 0.1f) {
|
|
45
|
+
velocityY = 0f
|
|
46
|
+
posY = groundY
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
) {
|
|
51
|
+
modelInstance?.let { instance ->
|
|
52
|
+
ModelNode(
|
|
53
|
+
modelInstance = instance,
|
|
54
|
+
scaleToUnits = 0.5f,
|
|
55
|
+
centerOrigin = Position(0f, 0f, 0f),
|
|
56
|
+
position = Position(0f, posY, 0f)
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Ground plane
|
|
61
|
+
CubeNode(
|
|
62
|
+
engine = engine,
|
|
63
|
+
size = Size(10f, 0.01f, 10f),
|
|
64
|
+
center = Position(0f, 0f, 0f),
|
|
65
|
+
materialInstance = rememberMaterialInstance(engine) {
|
|
66
|
+
baseColor(0.3f, 0.3f, 0.3f)
|
|
67
|
+
roughness(0.8f)
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
LightNode(
|
|
72
|
+
engine = engine,
|
|
73
|
+
type = LightManager.Type.DIRECTIONAL,
|
|
74
|
+
apply = {
|
|
75
|
+
intensity(100_000f)
|
|
76
|
+
castShadows(true)
|
|
77
|
+
direction(0f, -1f, -0.5f)
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
}`,
|
|
82
|
+
ios: `import SwiftUI
|
|
83
|
+
import SceneViewSwift
|
|
84
|
+
import RealityKit
|
|
85
|
+
|
|
86
|
+
struct GravityDropScene: View {
|
|
87
|
+
@State private var posY: Float = 3.0
|
|
88
|
+
@State private var velocityY: Float = 0
|
|
89
|
+
|
|
90
|
+
let gravity: Float = -9.81
|
|
91
|
+
let bounceFactor: Float = 0.6
|
|
92
|
+
let timer = Timer.publish(every: 1.0/60.0, on: .main, in: .common).autoconnect()
|
|
93
|
+
|
|
94
|
+
var body: some View {
|
|
95
|
+
SceneView { root in
|
|
96
|
+
// Add ground plane
|
|
97
|
+
let ground = ModelEntity(
|
|
98
|
+
mesh: .generatePlane(width: 10, depth: 10),
|
|
99
|
+
materials: [SimpleMaterial(color: .gray, isMetallic: false)]
|
|
100
|
+
)
|
|
101
|
+
root.addChild(ground)
|
|
102
|
+
}
|
|
103
|
+
.onReceive(timer) { _ in
|
|
104
|
+
let dt: Float = 1.0 / 60.0
|
|
105
|
+
velocityY += gravity * dt
|
|
106
|
+
posY += velocityY * dt
|
|
107
|
+
if posY <= 0 {
|
|
108
|
+
posY = 0
|
|
109
|
+
velocityY = -velocityY * bounceFactor
|
|
110
|
+
if abs(velocityY) < 0.1 {
|
|
111
|
+
velocityY = 0
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}`,
|
|
117
|
+
},
|
|
118
|
+
"collision-detection": {
|
|
119
|
+
title: "Collision Detection",
|
|
120
|
+
description: "Detects collisions between objects using bounding box intersection from sceneview-core KMP.",
|
|
121
|
+
android: `@Composable
|
|
122
|
+
fun CollisionDetectionScene() {
|
|
123
|
+
val engine = rememberEngine()
|
|
124
|
+
val modelLoader = rememberModelLoader(engine)
|
|
125
|
+
val modelA = rememberModelInstance(modelLoader, "models/sphere.glb")
|
|
126
|
+
val modelB = rememberModelInstance(modelLoader, "models/cube.glb")
|
|
127
|
+
|
|
128
|
+
var posAx by remember { mutableFloatStateOf(-2f) }
|
|
129
|
+
var isColliding by remember { mutableStateOf(false) }
|
|
130
|
+
val speed = 1.5f
|
|
131
|
+
|
|
132
|
+
SceneView(
|
|
133
|
+
modifier = Modifier.fillMaxSize(),
|
|
134
|
+
engine = engine,
|
|
135
|
+
modelLoader = modelLoader,
|
|
136
|
+
onFrame = { _ ->
|
|
137
|
+
// Move sphere toward cube
|
|
138
|
+
if (posAx < 2f && !isColliding) {
|
|
139
|
+
posAx += speed * (1f / 60f)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Simple AABB collision check
|
|
143
|
+
// Object A center at (posAx, 0, 0) with radius ~0.5
|
|
144
|
+
// Object B center at (1, 0, 0) with radius ~0.5
|
|
145
|
+
val distance = kotlin.math.abs(posAx - 1f)
|
|
146
|
+
isColliding = distance < 1.0f // Combined radii
|
|
147
|
+
}
|
|
148
|
+
) {
|
|
149
|
+
// Moving sphere
|
|
150
|
+
modelA?.let { instance ->
|
|
151
|
+
ModelNode(
|
|
152
|
+
modelInstance = instance,
|
|
153
|
+
scaleToUnits = 0.5f,
|
|
154
|
+
position = Position(posAx, 0.5f, 0f)
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Static cube
|
|
159
|
+
modelB?.let { instance ->
|
|
160
|
+
ModelNode(
|
|
161
|
+
modelInstance = instance,
|
|
162
|
+
scaleToUnits = 0.5f,
|
|
163
|
+
position = Position(1f, 0.5f, 0f)
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
LightNode(
|
|
168
|
+
engine = engine,
|
|
169
|
+
type = LightManager.Type.DIRECTIONAL,
|
|
170
|
+
apply = {
|
|
171
|
+
intensity(100_000f)
|
|
172
|
+
direction(0f, -1f, -0.5f)
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Collision feedback
|
|
178
|
+
if (isColliding) {
|
|
179
|
+
Text(
|
|
180
|
+
text = "COLLISION!",
|
|
181
|
+
modifier = Modifier.padding(16.dp),
|
|
182
|
+
color = Color.Red,
|
|
183
|
+
style = MaterialTheme.typography.headlineMedium
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
}`,
|
|
187
|
+
},
|
|
188
|
+
"spring-physics": {
|
|
189
|
+
title: "Spring Physics (KMP Core)",
|
|
190
|
+
description: "Uses Spring animation from sceneview-core KMP for physically-based bounce and wobble.",
|
|
191
|
+
android: `@Composable
|
|
192
|
+
fun SpringPhysicsScene() {
|
|
193
|
+
val engine = rememberEngine()
|
|
194
|
+
val modelLoader = rememberModelLoader(engine)
|
|
195
|
+
val modelInstance = rememberModelInstance(modelLoader, "models/object.glb")
|
|
196
|
+
|
|
197
|
+
// Spring parameters
|
|
198
|
+
var targetY by remember { mutableFloatStateOf(1.0f) }
|
|
199
|
+
var currentY by remember { mutableFloatStateOf(0f) }
|
|
200
|
+
var velocityY by remember { mutableFloatStateOf(0f) }
|
|
201
|
+
val stiffness = 200f // Higher = snappier
|
|
202
|
+
val damping = 10f // Higher = less bounce
|
|
203
|
+
val mass = 1f
|
|
204
|
+
|
|
205
|
+
// Toggle target on tap
|
|
206
|
+
var raised by remember { mutableStateOf(false) }
|
|
207
|
+
|
|
208
|
+
SceneView(
|
|
209
|
+
modifier = Modifier
|
|
210
|
+
.fillMaxSize()
|
|
211
|
+
.clickable {
|
|
212
|
+
raised = !raised
|
|
213
|
+
targetY = if (raised) 2.0f else 1.0f
|
|
214
|
+
},
|
|
215
|
+
engine = engine,
|
|
216
|
+
modelLoader = modelLoader,
|
|
217
|
+
onFrame = { _ ->
|
|
218
|
+
val dt = 1f / 60f
|
|
219
|
+
// Spring force: F = -k * (x - target) - d * v
|
|
220
|
+
val springForce = -stiffness * (currentY - targetY) - damping * velocityY
|
|
221
|
+
val acceleration = springForce / mass
|
|
222
|
+
velocityY += acceleration * dt
|
|
223
|
+
currentY += velocityY * dt
|
|
224
|
+
}
|
|
225
|
+
) {
|
|
226
|
+
modelInstance?.let { instance ->
|
|
227
|
+
ModelNode(
|
|
228
|
+
modelInstance = instance,
|
|
229
|
+
scaleToUnits = 0.5f,
|
|
230
|
+
centerOrigin = Position(0f, 0f, 0f),
|
|
231
|
+
position = Position(0f, currentY, 0f)
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Ground
|
|
236
|
+
CubeNode(
|
|
237
|
+
engine = engine,
|
|
238
|
+
size = Size(5f, 0.01f, 5f),
|
|
239
|
+
center = Position(0f, 0f, 0f),
|
|
240
|
+
materialInstance = rememberMaterialInstance(engine) {
|
|
241
|
+
baseColor(0.4f, 0.4f, 0.4f)
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
LightNode(
|
|
246
|
+
engine = engine,
|
|
247
|
+
type = LightManager.Type.DIRECTIONAL,
|
|
248
|
+
apply = {
|
|
249
|
+
intensity(100_000f)
|
|
250
|
+
castShadows(true)
|
|
251
|
+
direction(0f, -1f, -0.5f)
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
}`,
|
|
256
|
+
},
|
|
257
|
+
"projectile": {
|
|
258
|
+
title: "Projectile Motion",
|
|
259
|
+
description: "Launches a projectile with initial velocity, gravity, and trajectory tracking.",
|
|
260
|
+
android: `@Composable
|
|
261
|
+
fun ProjectileScene() {
|
|
262
|
+
val engine = rememberEngine()
|
|
263
|
+
val modelLoader = rememberModelLoader(engine)
|
|
264
|
+
val projectileModel = rememberModelInstance(modelLoader, "models/sphere.glb")
|
|
265
|
+
|
|
266
|
+
data class Projectile(var x: Float, var y: Float, var vx: Float, var vy: Float, var active: Boolean = true)
|
|
267
|
+
val projectiles = remember { mutableStateListOf<Projectile>() }
|
|
268
|
+
val gravity = -9.81f
|
|
269
|
+
|
|
270
|
+
// Launch on tap
|
|
271
|
+
SceneView(
|
|
272
|
+
modifier = Modifier
|
|
273
|
+
.fillMaxSize()
|
|
274
|
+
.clickable {
|
|
275
|
+
projectiles.add(
|
|
276
|
+
Projectile(
|
|
277
|
+
x = 0f, y = 0.5f,
|
|
278
|
+
vx = 3f + (Math.random() * 2f).toFloat(),
|
|
279
|
+
vy = 5f + (Math.random() * 3f).toFloat()
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
},
|
|
283
|
+
engine = engine,
|
|
284
|
+
modelLoader = modelLoader,
|
|
285
|
+
onFrame = { _ ->
|
|
286
|
+
val dt = 1f / 60f
|
|
287
|
+
projectiles.forEachIndexed { index, p ->
|
|
288
|
+
if (p.active) {
|
|
289
|
+
p.vy += gravity * dt
|
|
290
|
+
p.x += p.vx * dt
|
|
291
|
+
p.y += p.vy * dt
|
|
292
|
+
if (p.y < -1f) {
|
|
293
|
+
p.active = false
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
) {
|
|
299
|
+
// Render active projectiles
|
|
300
|
+
projectiles.filter { it.active }.forEach { p ->
|
|
301
|
+
projectileModel?.let { instance ->
|
|
302
|
+
ModelNode(
|
|
303
|
+
modelInstance = instance,
|
|
304
|
+
scaleToUnits = 0.1f,
|
|
305
|
+
position = Position(p.x, p.y, 0f)
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Ground
|
|
311
|
+
CubeNode(
|
|
312
|
+
engine = engine,
|
|
313
|
+
size = Size(20f, 0.01f, 5f),
|
|
314
|
+
center = Position(5f, 0f, 0f),
|
|
315
|
+
materialInstance = rememberMaterialInstance(engine) {
|
|
316
|
+
baseColor(0.3f, 0.6f, 0.3f)
|
|
317
|
+
roughness(0.9f)
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
LightNode(
|
|
322
|
+
engine = engine,
|
|
323
|
+
type = LightManager.Type.DIRECTIONAL,
|
|
324
|
+
apply = {
|
|
325
|
+
intensity(100_000f)
|
|
326
|
+
castShadows(true)
|
|
327
|
+
direction(0f, -1f, -0.5f)
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
}`,
|
|
332
|
+
},
|
|
333
|
+
"ragdoll-basic": {
|
|
334
|
+
title: "Basic Ragdoll Joints",
|
|
335
|
+
description: "Simulates connected rigid bodies with joint constraints for ragdoll-like behavior.",
|
|
336
|
+
android: `@Composable
|
|
337
|
+
fun RagdollScene() {
|
|
338
|
+
val engine = rememberEngine()
|
|
339
|
+
|
|
340
|
+
data class RagdollPart(
|
|
341
|
+
var x: Float, var y: Float,
|
|
342
|
+
var vx: Float = 0f, var vy: Float = 0f,
|
|
343
|
+
val parentIndex: Int = -1,
|
|
344
|
+
val jointLength: Float = 0.3f
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
// Head, torso, left arm, right arm, left leg, right leg
|
|
348
|
+
val parts = remember {
|
|
349
|
+
mutableStateListOf(
|
|
350
|
+
RagdollPart(0f, 2.0f), // 0: Head
|
|
351
|
+
RagdollPart(0f, 1.5f), // 1: Torso (connected to head)
|
|
352
|
+
RagdollPart(-0.3f, 1.5f, parentIndex = 1, jointLength = 0.4f), // 2: Left arm
|
|
353
|
+
RagdollPart(0.3f, 1.5f, parentIndex = 1, jointLength = 0.4f), // 3: Right arm
|
|
354
|
+
RagdollPart(-0.1f, 1.0f, parentIndex = 1, jointLength = 0.5f), // 4: Left leg
|
|
355
|
+
RagdollPart(0.1f, 1.0f, parentIndex = 1, jointLength = 0.5f), // 5: Right leg
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
val gravity = -9.81f
|
|
360
|
+
val damping = 0.98f
|
|
361
|
+
|
|
362
|
+
SceneView(
|
|
363
|
+
modifier = Modifier.fillMaxSize(),
|
|
364
|
+
engine = engine,
|
|
365
|
+
onFrame = { _ ->
|
|
366
|
+
val dt = 1f / 60f
|
|
367
|
+
parts.forEachIndexed { index, part ->
|
|
368
|
+
// Apply gravity
|
|
369
|
+
part.vy += gravity * dt
|
|
370
|
+
part.x += part.vx * dt
|
|
371
|
+
part.y += part.vy * dt
|
|
372
|
+
part.vx *= damping
|
|
373
|
+
part.vy *= damping
|
|
374
|
+
|
|
375
|
+
// Ground constraint
|
|
376
|
+
if (part.y < 0f) {
|
|
377
|
+
part.y = 0f
|
|
378
|
+
part.vy = -part.vy * 0.3f
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Joint constraint
|
|
382
|
+
if (part.parentIndex >= 0) {
|
|
383
|
+
val parent = parts[part.parentIndex]
|
|
384
|
+
val dx = part.x - parent.x
|
|
385
|
+
val dy = part.y - parent.y
|
|
386
|
+
val dist = kotlin.math.sqrt(dx * dx + dy * dy)
|
|
387
|
+
if (dist > part.jointLength && dist > 0.001f) {
|
|
388
|
+
val correction = (dist - part.jointLength) / dist * 0.5f
|
|
389
|
+
part.x -= dx * correction
|
|
390
|
+
part.y -= dy * correction
|
|
391
|
+
parts[part.parentIndex] = parent.copy(
|
|
392
|
+
x = parent.x + dx * correction,
|
|
393
|
+
y = parent.y + dy * correction
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
) {
|
|
400
|
+
// Render each part as a sphere
|
|
401
|
+
parts.forEach { part ->
|
|
402
|
+
SphereNode(
|
|
403
|
+
engine = engine,
|
|
404
|
+
radius = 0.08f,
|
|
405
|
+
center = Position(part.x, part.y, 0f),
|
|
406
|
+
materialInstance = rememberMaterialInstance(engine) {
|
|
407
|
+
baseColor(0.9f, 0.6f, 0.4f)
|
|
408
|
+
roughness(0.7f)
|
|
409
|
+
}
|
|
410
|
+
)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
LightNode(
|
|
414
|
+
engine = engine,
|
|
415
|
+
type = LightManager.Type.DIRECTIONAL,
|
|
416
|
+
apply = {
|
|
417
|
+
intensity(100_000f)
|
|
418
|
+
direction(0f, -1f, -0.5f)
|
|
419
|
+
}
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
}`,
|
|
423
|
+
},
|
|
424
|
+
"rigid-body": {
|
|
425
|
+
title: "Rigid Body Simulation",
|
|
426
|
+
description: "Multiple rigid bodies with gravity, ground collision, and inter-body collision response.",
|
|
427
|
+
android: `@Composable
|
|
428
|
+
fun RigidBodyScene() {
|
|
429
|
+
val engine = rememberEngine()
|
|
430
|
+
|
|
431
|
+
data class RigidBody(
|
|
432
|
+
var x: Float, var y: Float, var z: Float,
|
|
433
|
+
var vx: Float = 0f, var vy: Float = 0f, var vz: Float = 0f,
|
|
434
|
+
val radius: Float = 0.25f,
|
|
435
|
+
val mass: Float = 1f,
|
|
436
|
+
val restitution: Float = 0.5f
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
val bodies = remember {
|
|
440
|
+
mutableStateListOf(
|
|
441
|
+
RigidBody(0f, 3f, 0f),
|
|
442
|
+
RigidBody(0.5f, 4f, 0.2f),
|
|
443
|
+
RigidBody(-0.3f, 5f, -0.1f),
|
|
444
|
+
RigidBody(0.1f, 6f, 0.3f),
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
val gravity = -9.81f
|
|
449
|
+
|
|
450
|
+
SceneView(
|
|
451
|
+
modifier = Modifier.fillMaxSize(),
|
|
452
|
+
engine = engine,
|
|
453
|
+
onFrame = { _ ->
|
|
454
|
+
val dt = 1f / 60f
|
|
455
|
+
bodies.forEachIndexed { i, body ->
|
|
456
|
+
// Gravity
|
|
457
|
+
body.vy += gravity * dt
|
|
458
|
+
body.x += body.vx * dt
|
|
459
|
+
body.y += body.vy * dt
|
|
460
|
+
body.z += body.vz * dt
|
|
461
|
+
|
|
462
|
+
// Ground bounce
|
|
463
|
+
if (body.y < body.radius) {
|
|
464
|
+
body.y = body.radius
|
|
465
|
+
body.vy = -body.vy * body.restitution
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Inter-body collision
|
|
469
|
+
for (j in (i + 1) until bodies.size) {
|
|
470
|
+
val other = bodies[j]
|
|
471
|
+
val dx = body.x - other.x
|
|
472
|
+
val dy = body.y - other.y
|
|
473
|
+
val dz = body.z - other.z
|
|
474
|
+
val dist = kotlin.math.sqrt(dx * dx + dy * dy + dz * dz)
|
|
475
|
+
val minDist = body.radius + other.radius
|
|
476
|
+
if (dist < minDist && dist > 0.001f) {
|
|
477
|
+
// Push apart
|
|
478
|
+
val nx = dx / dist
|
|
479
|
+
val ny = dy / dist
|
|
480
|
+
val nz = dz / dist
|
|
481
|
+
val overlap = (minDist - dist) * 0.5f
|
|
482
|
+
body.x += nx * overlap
|
|
483
|
+
body.y += ny * overlap
|
|
484
|
+
body.z += nz * overlap
|
|
485
|
+
other.x -= nx * overlap
|
|
486
|
+
other.y -= ny * overlap
|
|
487
|
+
other.z -= nz * overlap
|
|
488
|
+
|
|
489
|
+
// Elastic collision response
|
|
490
|
+
val relVx = body.vx - other.vx
|
|
491
|
+
val relVy = body.vy - other.vy
|
|
492
|
+
val relVz = body.vz - other.vz
|
|
493
|
+
val relVn = relVx * nx + relVy * ny + relVz * nz
|
|
494
|
+
if (relVn < 0) {
|
|
495
|
+
val impulse = relVn / (1f / body.mass + 1f / other.mass)
|
|
496
|
+
body.vx -= impulse / body.mass * nx
|
|
497
|
+
body.vy -= impulse / body.mass * ny
|
|
498
|
+
body.vz -= impulse / body.mass * nz
|
|
499
|
+
other.vx += impulse / other.mass * nx
|
|
500
|
+
other.vy += impulse / other.mass * ny
|
|
501
|
+
other.vz += impulse / other.mass * nz
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
) {
|
|
508
|
+
bodies.forEach { body ->
|
|
509
|
+
SphereNode(
|
|
510
|
+
engine = engine,
|
|
511
|
+
radius = body.radius,
|
|
512
|
+
center = Position(body.x, body.y, body.z),
|
|
513
|
+
materialInstance = rememberMaterialInstance(engine) {
|
|
514
|
+
baseColor(0.2f, 0.5f, 0.9f)
|
|
515
|
+
metallic(0.3f)
|
|
516
|
+
roughness(0.4f)
|
|
517
|
+
}
|
|
518
|
+
)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Ground
|
|
522
|
+
CubeNode(
|
|
523
|
+
engine = engine,
|
|
524
|
+
size = Size(10f, 0.02f, 10f),
|
|
525
|
+
center = Position(0f, 0f, 0f),
|
|
526
|
+
materialInstance = rememberMaterialInstance(engine) {
|
|
527
|
+
baseColor(0.4f, 0.4f, 0.4f)
|
|
528
|
+
roughness(0.9f)
|
|
529
|
+
}
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
LightNode(
|
|
533
|
+
engine = engine,
|
|
534
|
+
type = LightManager.Type.DIRECTIONAL,
|
|
535
|
+
apply = {
|
|
536
|
+
intensity(100_000f)
|
|
537
|
+
castShadows(true)
|
|
538
|
+
direction(0f, -1f, -0.5f)
|
|
539
|
+
}
|
|
540
|
+
)
|
|
541
|
+
}
|
|
542
|
+
}`,
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
export function generatePhysicsCode(physicsType, platform = "android") {
|
|
546
|
+
const template = PHYSICS_TEMPLATES[physicsType];
|
|
547
|
+
if (!template)
|
|
548
|
+
return null;
|
|
549
|
+
const code = platform === "ios" && template.ios ? template.ios : template.android;
|
|
550
|
+
return { code, title: template.title, description: template.description };
|
|
551
|
+
}
|
|
552
|
+
export function formatPhysicsCode(result, platform) {
|
|
553
|
+
const lang = platform === "ios" ? "swift" : "kotlin";
|
|
554
|
+
const platLabel = platform === "ios" ? "iOS (SwiftUI + RealityKit)" : "Android (Jetpack Compose)";
|
|
555
|
+
return [
|
|
556
|
+
`## ${result.title}`,
|
|
557
|
+
``,
|
|
558
|
+
`**Platform:** ${platLabel}`,
|
|
559
|
+
``,
|
|
560
|
+
result.description,
|
|
561
|
+
``,
|
|
562
|
+
`\`\`\`${lang}`,
|
|
563
|
+
result.code,
|
|
564
|
+
`\`\`\``,
|
|
565
|
+
``,
|
|
566
|
+
`### Available Physics Types`,
|
|
567
|
+
``,
|
|
568
|
+
...PHYSICS_TYPES.map((t) => `- \`${t}\``),
|
|
569
|
+
].join("\n");
|
|
570
|
+
}
|
package/dist/generate-scene.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* generate-scene.ts
|
|
3
3
|
*
|
|
4
|
-
* Generates a complete
|
|
4
|
+
* Generates a complete SceneView{} or ARSceneView{} composable from a text description.
|
|
5
5
|
* Maps common objects/concepts to SceneView node types and builds compilable code.
|
|
6
6
|
*
|
|
7
|
-
* All generated code targets SceneView v3.6.
|
|
7
|
+
* All generated code targets SceneView v3.6.2 API and is verified against llms.txt.
|
|
8
8
|
*/
|
|
9
9
|
const OBJECT_MAPPINGS = [
|
|
10
10
|
// Furniture
|
|
@@ -245,7 +245,7 @@ export function generateScene(description) {
|
|
|
245
245
|
}
|
|
246
246
|
// Build the code
|
|
247
247
|
const isAR = parsed.isAR;
|
|
248
|
-
dependencies.push(isAR ? "io.github.sceneview:arsceneview:3.6.
|
|
248
|
+
dependencies.push(isAR ? "io.github.sceneview:arsceneview:3.6.2" : "io.github.sceneview:sceneview:3.6.2");
|
|
249
249
|
// Build model instance declarations
|
|
250
250
|
const modelElements = elements.filter((e) => e.type === "model");
|
|
251
251
|
const uniqueModels = new Map();
|
|
@@ -296,7 +296,7 @@ export function generateScene(description) {
|
|
|
296
296
|
lines.push(" }");
|
|
297
297
|
lines.push("");
|
|
298
298
|
}
|
|
299
|
-
//
|
|
299
|
+
// SceneView or ARSceneView
|
|
300
300
|
if (isAR) {
|
|
301
301
|
lines.push(" var anchor by remember { mutableStateOf<Anchor?>(null) }");
|
|
302
302
|
lines.push("");
|