lens-inspector 0.1.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/example.js ADDED
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Example: simulated Lens Studio scene for the inspector.
4
+ *
5
+ * Sends a scene graph with a representative mix of LS component types:
6
+ * cameras, lights, scripts, physics, audio, visuals, UI, and runtime-
7
+ * spawned objects. This lets you try the viewer without Lens Studio.
8
+ *
9
+ * Usage:
10
+ * node server.js # terminal 1
11
+ * node example.js # terminal 2
12
+ */
13
+
14
+ import WebSocket from "ws";
15
+
16
+ const URL = process.argv[2] || "ws://localhost:8200?role=ls";
17
+
18
+ let frame = 0;
19
+ let spawned = [];
20
+ const spawnInterval = 5;
21
+ const maxBlocks = 10;
22
+ let spawnCount = 0;
23
+
24
+ function obj(name, opts = {}) {
25
+ return {
26
+ name,
27
+ enabled: opts.enabled !== false,
28
+ category: opts.category || "empty",
29
+ pos: opts.pos || [0, 0, 0],
30
+ scale: opts.scale || [1, 1, 1],
31
+ rot: opts.rot || [0, 0, 0, 1],
32
+ components: opts.components || [],
33
+ text: opts.text || null,
34
+ hasVisual: opts.hasVisual || false,
35
+ color: opts.color || null,
36
+ layer: opts.layer || null,
37
+ children: opts.children || [],
38
+ };
39
+ }
40
+
41
+ function comp(type, extra = {}) {
42
+ return { type, enabled: true, category: null, ...extra };
43
+ }
44
+
45
+ function buildScene() {
46
+ const t = frame * 0.5;
47
+
48
+ // Spawn runtime blocks
49
+ if (frame > 0 && frame % spawnInterval === 0 && spawned.length < maxBlocks) {
50
+ spawnCount++;
51
+ spawned.push({
52
+ name: `Block_${spawnCount}`,
53
+ x: (Math.random() - 0.5) * 30,
54
+ z: 50 + (Math.random() - 0.5) * 20,
55
+ yTarget: -7 + Math.random() * 16,
56
+ yStart: 20 + Math.random() * 10,
57
+ born: t,
58
+ w: 2 + Math.random() * 4,
59
+ h: 1 + Math.random() * 6,
60
+ });
61
+ }
62
+ while (spawned.length > maxBlocks) spawned.shift();
63
+
64
+ const spawnedNodes = spawned.map((b, i) => {
65
+ const age = t - b.born;
66
+ const settle = Math.min(age * 2, 1);
67
+ const y = b.yStart + (b.yTarget - b.yStart) * settle;
68
+ const isNewest = i === spawned.length - 1;
69
+ const rotY = isNewest ? (t * 15 % 360) : 0;
70
+
71
+ return obj(b.name, {
72
+ category: "visual",
73
+ pos: [+b.x.toFixed(2), +y.toFixed(2), +b.z.toFixed(2)],
74
+ scale: [+b.w.toFixed(2), +b.h.toFixed(2), +b.w.toFixed(2)],
75
+ rot: [0, +((rotY * Math.PI / 180) * 0.01).toFixed(3), 0, 1],
76
+ components: [
77
+ comp("RenderMeshVisual", { meshName: "Box" }),
78
+ comp("BodyComponent", { dynamic: true, mass: 1.0 }),
79
+ comp("ColliderComponent"),
80
+ ],
81
+ hasVisual: true,
82
+ });
83
+ });
84
+
85
+ const scene = {
86
+ event: "scene_snapshot",
87
+ ts: Date.now(),
88
+ roots: [
89
+ // Perspective Camera with scripts
90
+ obj("Camera", {
91
+ category: "camera",
92
+ pos: [0, 8, -20],
93
+ rot: [-0.15, 0, 0, 1],
94
+ components: [
95
+ comp("Camera", { cameraType: "perspective", fov: 60, near: 0.1, far: 1000, renderOrder: 0 }),
96
+ comp("ScriptComponent", { scriptName: "SceneInspector" }),
97
+ comp("DeviceTracking"),
98
+ ],
99
+ }),
100
+
101
+ // Orthographic camera for UI
102
+ obj("Ortho Camera", {
103
+ category: "camera",
104
+ components: [
105
+ comp("Camera", { cameraType: "orthographic", orthoSize: 20, renderOrder: 1 }),
106
+ ],
107
+ children: [
108
+ obj("Screen Region", {
109
+ category: "ui",
110
+ components: [comp("ScreenTransform"), comp("ScreenRegionComponent")],
111
+ children: [
112
+ obj("Canvas", {
113
+ category: "ui",
114
+ components: [comp("Canvas")],
115
+ children: [
116
+ obj("Title", {
117
+ category: "text",
118
+ text: "Scene Inspector Demo",
119
+ components: [comp("Text", { text: "Scene Inspector Demo", textSize: 32 }), comp("ScreenTransform")],
120
+ }),
121
+ obj("Score", {
122
+ category: "text",
123
+ text: "0",
124
+ components: [comp("Text", { text: "0", textSize: 24 }), comp("ScreenTransform")],
125
+ }),
126
+ ],
127
+ }),
128
+ ],
129
+ }),
130
+ ],
131
+ }),
132
+
133
+ // Main scene content
134
+ obj("Scene", {
135
+ components: [comp("ScriptComponent", { scriptName: "RuntimeSpawner" })],
136
+ children: [
137
+ // Lighting
138
+ obj("Lights", {
139
+ children: [
140
+ obj("Sun", {
141
+ category: "light",
142
+ pos: [10, 20, -5],
143
+ rot: [-0.3, 0.2, 0, 1],
144
+ components: [comp("DirectionalLight")],
145
+ }),
146
+ obj("Fill", {
147
+ category: "light",
148
+ pos: [-8, 5, 10],
149
+ components: [comp("PointLight")],
150
+ }),
151
+ obj("Ambient", {
152
+ category: "light",
153
+ components: [comp("AmbientLight")],
154
+ }),
155
+ ],
156
+ }),
157
+
158
+ // Ground with physics
159
+ obj("Ground", {
160
+ category: "visual",
161
+ pos: [0, -8, 50],
162
+ scale: [40, 1, 40],
163
+ components: [
164
+ comp("RenderMeshVisual", { meshName: "Plane", materialName: "PBR Ground" }),
165
+ comp("ColliderComponent"),
166
+ ],
167
+ hasVisual: true,
168
+ color: [60, 65, 70, 255],
169
+ }),
170
+
171
+ // Architecture group
172
+ obj("Architecture", {
173
+ children: [
174
+ obj("Tower", {
175
+ pos: [-8, -7, 50],
176
+ children: [
177
+ obj("Base Block", {
178
+ category: "visual",
179
+ pos: [0, 0, 0], scale: [8, 6, 8],
180
+ components: [comp("RenderMeshVisual", { meshName: "Box" }), comp("ColliderComponent")],
181
+ hasVisual: true,
182
+ }),
183
+ obj("Mid Block", {
184
+ category: "visual",
185
+ pos: [1, 6, 0], scale: [6, 5, 6],
186
+ components: [comp("RenderMeshVisual", { meshName: "Box" })],
187
+ hasVisual: true,
188
+ }),
189
+ obj("Top Block", {
190
+ category: "visual",
191
+ pos: [-1, 11, 1], scale: [4, 4, 4],
192
+ components: [comp("RenderMeshVisual", { meshName: "Box" })],
193
+ hasVisual: true,
194
+ }),
195
+ ],
196
+ }),
197
+
198
+ obj("Bridge", {
199
+ pos: [8, -7, 50],
200
+ children: [
201
+ obj("Pillar L", {
202
+ category: "visual",
203
+ pos: [-4, 0, 0], scale: [2, 8, 2],
204
+ components: [comp("RenderMeshVisual", { meshName: "Box" }), comp("ColliderComponent")],
205
+ hasVisual: true,
206
+ }),
207
+ obj("Pillar R", {
208
+ category: "visual",
209
+ pos: [4, 0, 0], scale: [2, 8, 2],
210
+ components: [comp("RenderMeshVisual", { meshName: "Box" }), comp("ColliderComponent")],
211
+ hasVisual: true,
212
+ }),
213
+ obj("Beam", {
214
+ category: "visual",
215
+ pos: [0, 8, 0], scale: [10, 1.5, 3],
216
+ components: [comp("RenderMeshVisual", { meshName: "Box" })],
217
+ hasVisual: true,
218
+ }),
219
+ ],
220
+ }),
221
+ ],
222
+ }),
223
+
224
+ // Interactive objects
225
+ obj("Interactables", {
226
+ children: [
227
+ obj("Grab Cube", {
228
+ category: "interaction",
229
+ pos: [0, 0, 45],
230
+ scale: [3, 3, 3],
231
+ components: [
232
+ comp("RenderMeshVisual", { meshName: "Box" }),
233
+ comp("InteractionComponent"),
234
+ comp("ManipulateComponent"),
235
+ comp("BodyComponent", { dynamic: true, mass: 0.5 }),
236
+ comp("ColliderComponent"),
237
+ ],
238
+ hasVisual: true,
239
+ color: [74, 144, 184, 255],
240
+ }),
241
+ obj("Tap Sphere", {
242
+ category: "interaction",
243
+ pos: [6, 2, 48],
244
+ scale: [2, 2, 2],
245
+ components: [
246
+ comp("RenderMeshVisual", { meshName: "Sphere" }),
247
+ comp("InteractionComponent"),
248
+ comp("AnimationMixer"),
249
+ ],
250
+ hasVisual: true,
251
+ color: [97, 175, 239, 255],
252
+ }),
253
+ ],
254
+ }),
255
+
256
+ // Audio
257
+ obj("Audio Sources", {
258
+ children: [
259
+ obj("Background Music", {
260
+ category: "audio",
261
+ components: [comp("AudioComponent", { trackName: "ambient_loop.wav", volume: 0.3 })],
262
+ }),
263
+ obj("SFX Emitter", {
264
+ category: "audio",
265
+ pos: [0, 0, 50],
266
+ components: [comp("AudioComponent", { trackName: "click.wav", volume: 1.0 }), comp("AudioListenerComponent")],
267
+ }),
268
+ ],
269
+ }),
270
+
271
+ // VFX
272
+ obj("Particle Emitter", {
273
+ category: "vfx",
274
+ pos: [-1, 14, 51],
275
+ components: [comp("VFXComponent")],
276
+ }),
277
+
278
+ // Tracking
279
+ obj("Hand Tracker", {
280
+ category: "tracking",
281
+ components: [comp("ObjectTracking3D")],
282
+ children: [
283
+ obj("Hand Visual", {
284
+ category: "visual",
285
+ components: [comp("RenderMeshVisual", { meshName: "HandMesh" })],
286
+ hasVisual: true,
287
+ }),
288
+ ],
289
+ }),
290
+ ],
291
+ }),
292
+
293
+ // Runtime container
294
+ obj("[Spawned]", {
295
+ children: spawnedNodes,
296
+ }),
297
+ ],
298
+ totalObjects: 0,
299
+ };
300
+
301
+ function count(nodes) {
302
+ let c = 0;
303
+ for (const n of nodes) c += 1 + count(n.children || []);
304
+ return c;
305
+ }
306
+ scene.totalObjects = count(scene.roots);
307
+ return scene;
308
+ }
309
+
310
+ function connect() {
311
+ const ws = new WebSocket(URL);
312
+
313
+ ws.on("open", () => {
314
+ console.log("Connected to inspector server");
315
+ console.log("Open http://localhost:8200 to see the scene");
316
+ console.log("Streaming scene graph... (Ctrl+C to stop)\n");
317
+
318
+ const interval = setInterval(() => {
319
+ if (ws.readyState !== WebSocket.OPEN) { clearInterval(interval); return; }
320
+ const scene = buildScene();
321
+ ws.send(JSON.stringify(scene));
322
+ frame++;
323
+ if (frame % 30 === 0) {
324
+ console.log(` frame ${frame} | ${scene.totalObjects} objects (${spawned.length} spawned)`);
325
+ }
326
+ }, 1000 / 2);
327
+ });
328
+
329
+ ws.on("close", () => {
330
+ console.log("Disconnected, reconnecting in 2s...");
331
+ setTimeout(connect, 2000);
332
+ });
333
+
334
+ ws.on("error", (e) => {
335
+ console.error("Connection error:", e.message);
336
+ });
337
+ }
338
+
339
+ connect();