@vibes.diy/prompts 2.2.17 → 2.2.19
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 +1 -1
- package/json-docs.d.ts +1 -0
- package/json-docs.js.map +1 -1
- package/llms/index.d.ts +2 -1
- package/llms/index.js +3 -0
- package/llms/index.js.map +1 -1
- package/llms/three-js.js +1 -1
- package/llms/three-js.js.map +1 -1
- package/llms/webxr.d.ts +2 -0
- package/llms/webxr.js +14 -0
- package/llms/webxr.js.map +1 -0
- package/llms/webxr.md +751 -0
- package/package.json +3 -3
- package/prompts.d.ts +2 -2
- package/system-prompt.md +1 -1
package/README.md
CHANGED
package/json-docs.d.ts
CHANGED
package/json-docs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-docs.js","sourceRoot":"","sources":["../jsr/json-docs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"json-docs.js","sourceRoot":"","sources":["../jsr/json-docs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAwB7C,MAAM,UAAU,kBAAkB;IAChC,OAAO,aAAa,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAChF,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,eAAe,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QACjC,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,IAAuB,EAAE;IAC5D,MAAM,CAAC,GAAa,EAAc,CAAC;IAGnC,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,GAAG,MAAM,CAAC,IAAI,OAAO,CAAC;QACvC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IAChD,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC,CAAC,CAAC"}
|
package/llms/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { imageGenConfig } from "./image-gen.js";
|
|
|
4
4
|
export { webAudioConfig } from "./web-audio.js";
|
|
5
5
|
export { d3Config } from "./d3.js";
|
|
6
6
|
export { threeJsConfig } from "./three-js.js";
|
|
7
|
+
export { webxrConfig } from "./webxr.js";
|
|
7
8
|
export { useViewerConfig } from "./use-viewer.js";
|
|
8
9
|
export type { LlmConfig } from "./types.js";
|
|
9
|
-
export declare const allConfigs: readonly [import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig];
|
|
10
|
+
export declare const allConfigs: readonly [import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig, import("./types.js").LlmConfig];
|
package/llms/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { imageGenConfig } from "./image-gen.js";
|
|
|
4
4
|
import { webAudioConfig } from "./web-audio.js";
|
|
5
5
|
import { d3Config } from "./d3.js";
|
|
6
6
|
import { threeJsConfig } from "./three-js.js";
|
|
7
|
+
import { webxrConfig } from "./webxr.js";
|
|
7
8
|
import { useViewerConfig } from "./use-viewer.js";
|
|
8
9
|
export { callaiConfig } from "./callai.js";
|
|
9
10
|
export { fireproofConfig } from "./fireproof.js";
|
|
@@ -11,6 +12,7 @@ export { imageGenConfig } from "./image-gen.js";
|
|
|
11
12
|
export { webAudioConfig } from "./web-audio.js";
|
|
12
13
|
export { d3Config } from "./d3.js";
|
|
13
14
|
export { threeJsConfig } from "./three-js.js";
|
|
15
|
+
export { webxrConfig } from "./webxr.js";
|
|
14
16
|
export { useViewerConfig } from "./use-viewer.js";
|
|
15
17
|
export const allConfigs = [
|
|
16
18
|
callaiConfig,
|
|
@@ -19,6 +21,7 @@ export const allConfigs = [
|
|
|
19
21
|
d3Config,
|
|
20
22
|
threeJsConfig,
|
|
21
23
|
fireproofConfig,
|
|
24
|
+
webxrConfig,
|
|
22
25
|
useViewerConfig,
|
|
23
26
|
];
|
|
24
27
|
//# sourceMappingURL=index.js.map
|
package/llms/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../jsr/llms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAIlD,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,YAAY;IACZ,cAAc;IACd,cAAc;IACd,QAAQ;IACR,aAAa;IACb,eAAe;IACf,eAAe;CACP,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../jsr/llms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAIlD,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,YAAY;IACZ,cAAc;IACd,cAAc;IACd,QAAQ;IACR,aAAa;IACb,eAAe;IACf,WAAW;IACX,eAAe;CACP,CAAC"}
|
package/llms/three-js.js
CHANGED
|
@@ -2,7 +2,7 @@ export const threeJsConfig = {
|
|
|
2
2
|
name: "three-js",
|
|
3
3
|
label: "Three.js",
|
|
4
4
|
module: "three-js",
|
|
5
|
-
description: "Three.js 3D graphics library for WebGL
|
|
5
|
+
description: "Three.js 3D graphics library for WebGL rendering: mesh geometry, materials, lighting, animation, cameras, textures, GLSL shaders, GLTF/OBJ/FBX model loading, physics, particle systems, post-processing, visual effects, 3js",
|
|
6
6
|
importModule: "three",
|
|
7
7
|
importName: "THREE",
|
|
8
8
|
importType: "namespace",
|
package/llms/three-js.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"three-js.js","sourceRoot":"","sources":["../../jsr/llms/three-js.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAc;IACtC,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,UAAU;IACjB,MAAM,EAAE,UAAU;IAClB,WAAW,EACT
|
|
1
|
+
{"version":3,"file":"three-js.js","sourceRoot":"","sources":["../../jsr/llms/three-js.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAc;IACtC,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,UAAU;IACjB,MAAM,EAAE,UAAU;IAClB,WAAW,EACT,+NAA+N;IACjO,YAAY,EAAE,OAAO;IACrB,UAAU,EAAE,OAAO;IACnB,UAAU,EAAE,WAAW;CACxB,CAAC"}
|
package/llms/webxr.d.ts
ADDED
package/llms/webxr.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const webxrConfig = {
|
|
2
|
+
name: "webxr",
|
|
3
|
+
label: "Babylon.js WebXR",
|
|
4
|
+
module: "webxr",
|
|
5
|
+
description: "Babylon.js 3D engine with first-class WebXR: immersive VR and AR passthrough, " +
|
|
6
|
+
"hit-testing, surface anchors, particle systems, SolidParticleSystem, custom GLSL shaders, " +
|
|
7
|
+
"controller and hand-tracking events, PBR materials, Quest performance patterns. " +
|
|
8
|
+
"babylon, babylonjs, WebXR, VR, AR, spatial computing, immersive, mixed reality, xr, " +
|
|
9
|
+
"augmented reality, virtual reality, Quest, Vision Pro",
|
|
10
|
+
importModule: "@babylonjs/core",
|
|
11
|
+
importName: "BABYLON",
|
|
12
|
+
importType: "namespace",
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=webxr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webxr.js","sourceRoot":"","sources":["../../jsr/llms/webxr.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,WAAW,GAAc;IACpC,IAAI,EAAE,OAAO;IACb,KAAK,EAAE,kBAAkB;IACzB,MAAM,EAAE,OAAO;IACf,WAAW,EACT,gFAAgF;QAChF,4FAA4F;QAC5F,kFAAkF;QAClF,sFAAsF;QACtF,uDAAuD;IACzD,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,SAAS;IACrB,UAAU,EAAE,WAAW;CACxB,CAAC"}
|
package/llms/webxr.md
ADDED
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
# Babylon.js WebXR Reference for Coding Agents
|
|
2
|
+
|
|
3
|
+
_Babylon.js is a full-featured 3D engine with first-class WebXR support. Import it as a bare specifier — the platform resolves it via esm.sh._
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import * as BABYLON from "@babylonjs/core";
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Core Setup
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
const canvas = document.getElementById("renderCanvas");
|
|
15
|
+
const engine = new BABYLON.Engine(canvas, true, {
|
|
16
|
+
preserveDrawingBuffer: true,
|
|
17
|
+
stencil: true,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const scene = new BABYLON.Scene(engine);
|
|
21
|
+
|
|
22
|
+
// Required resize handler
|
|
23
|
+
window.addEventListener("resize", () => engine.resize());
|
|
24
|
+
|
|
25
|
+
// Render loop — put your per-frame logic in scene.onBeforeRenderObservable
|
|
26
|
+
engine.runRenderLoop(() => scene.render());
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Minimal Scene with Lighting
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
// Camera (required even in VR — sets the non-XR fallback view)
|
|
33
|
+
const camera = new BABYLON.FreeCamera("cam", new BABYLON.Vector3(0, 1.7, -5), scene);
|
|
34
|
+
camera.setTarget(BABYLON.Vector3.Zero());
|
|
35
|
+
camera.attachControl(canvas, true);
|
|
36
|
+
|
|
37
|
+
// Lights
|
|
38
|
+
const ambient = new BABYLON.HemisphericLight("ambient", new BABYLON.Vector3(0, 1, 0), scene);
|
|
39
|
+
ambient.intensity = 0.4;
|
|
40
|
+
|
|
41
|
+
const sun = new BABYLON.DirectionalLight("sun", new BABYLON.Vector3(-1, -2, -1), scene);
|
|
42
|
+
sun.position = new BABYLON.Vector3(5, 10, 5);
|
|
43
|
+
sun.intensity = 0.8;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## VR Mode
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
// createDefaultXRExperienceAsync is async — always await it
|
|
52
|
+
const xrHelper = await scene.createDefaultXRExperienceAsync({
|
|
53
|
+
floorMeshes: [ground], // meshes user can teleport onto
|
|
54
|
+
disableTeleportation: false, // set true to disable built-in teleport
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Access the base XR session
|
|
58
|
+
const sessionManager = xrHelper.baseExperience.sessionManager;
|
|
59
|
+
|
|
60
|
+
// Check if currently in XR
|
|
61
|
+
if (xrHelper.baseExperience.state === BABYLON.WebXRState.IN_XR) {
|
|
62
|
+
console.log("In VR");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// React to entering/leaving XR
|
|
66
|
+
xrHelper.baseExperience.onStateChangedObservable.add((state) => {
|
|
67
|
+
if (state === BABYLON.WebXRState.IN_XR) {
|
|
68
|
+
// User entered VR — hide 2D UI, etc.
|
|
69
|
+
}
|
|
70
|
+
if (state === BABYLON.WebXRState.NOT_IN_XR) {
|
|
71
|
+
// User exited VR
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### VR Button
|
|
77
|
+
|
|
78
|
+
`createDefaultXRExperienceAsync` adds the "Enter VR" button automatically. To add it manually:
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
const vrButton = BABYLON.WebXRDefaultExperience.CreateAsync(scene, {
|
|
82
|
+
uiOptions: { sessionMode: "immersive-vr" },
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## AR Mode (Passthrough)
|
|
89
|
+
|
|
90
|
+
AR mode requires HTTPS. The Quest Browser and Chrome on Android support it. Safari/Vision Pro does not yet support AR passthrough via WebXR.
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
const xrHelper = await scene.createDefaultXRExperienceAsync({
|
|
94
|
+
uiOptions: {
|
|
95
|
+
sessionMode: "immersive-ar",
|
|
96
|
+
referenceSpaceType: "local-floor",
|
|
97
|
+
},
|
|
98
|
+
optionalFeatures: true, // enables hit-test, anchors if supported
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Hit Testing (Tap to Place)
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
const hitTest = xrHelper.featuresManager.enableFeature(BABYLON.WebXRHitTest, "latest");
|
|
106
|
+
|
|
107
|
+
// Reusable indicator mesh (a thin ring)
|
|
108
|
+
const indicator = BABYLON.MeshBuilder.CreateTorus(
|
|
109
|
+
"indicator",
|
|
110
|
+
{
|
|
111
|
+
diameter: 0.3,
|
|
112
|
+
thickness: 0.01,
|
|
113
|
+
tessellation: 32,
|
|
114
|
+
},
|
|
115
|
+
scene
|
|
116
|
+
);
|
|
117
|
+
indicator.isPickable = false;
|
|
118
|
+
|
|
119
|
+
hitTest.onHitTestResultObservable.add((results) => {
|
|
120
|
+
if (results.length > 0) {
|
|
121
|
+
indicator.isVisible = true;
|
|
122
|
+
results[0].transformationMatrix.decompose(undefined, indicator.rotationQuaternion, indicator.position);
|
|
123
|
+
} else {
|
|
124
|
+
indicator.isVisible = false;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Anchors (Persist Placed Objects)
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
const anchors = xrHelper.featuresManager.enableFeature(BABYLON.WebXRAnchorSystem, "latest");
|
|
133
|
+
|
|
134
|
+
// Place an object anchored to a real-world surface
|
|
135
|
+
async function placeAnchor(hitTestResult) {
|
|
136
|
+
const anchor = await anchors.addAnchorPointUsingHitTestResultAsync(hitTestResult);
|
|
137
|
+
const mesh = BABYLON.MeshBuilder.CreateSphere("orb", { diameter: 0.15 }, scene);
|
|
138
|
+
anchor.attachedNode = mesh; // mesh follows the anchor
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Plane Detection
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
const planes = xrHelper.featuresManager.enableFeature(BABYLON.WebXRPlaneDetector, "latest");
|
|
146
|
+
|
|
147
|
+
planes.onPlaneAddedObservable.add((plane) => {
|
|
148
|
+
// plane.xrPlane.polygon — array of DOMPointReadOnly vertices
|
|
149
|
+
// plane.mesh — auto-generated Babylon mesh for the detected plane
|
|
150
|
+
plane.mesh.material = planeMaterial;
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Generative Art Patterns
|
|
157
|
+
|
|
158
|
+
### Particle System (Billboarded Sprites)
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
const particles = new BABYLON.ParticleSystem("stars", 3000, scene);
|
|
162
|
+
|
|
163
|
+
// Texture — use a built-in or a canvas texture
|
|
164
|
+
particles.particleTexture = new BABYLON.Texture("https://playground.babylonjs.com/textures/flare.png", scene);
|
|
165
|
+
|
|
166
|
+
particles.emitter = new BABYLON.Vector3(0, 1, 0); // world position
|
|
167
|
+
particles.minEmitBox = new BABYLON.Vector3(-5, -5, -5);
|
|
168
|
+
particles.maxEmitBox = new BABYLON.Vector3(5, 5, 5);
|
|
169
|
+
|
|
170
|
+
particles.color1 = new BABYLON.Color4(0.3, 0.8, 1, 1);
|
|
171
|
+
particles.color2 = new BABYLON.Color4(1, 0.3, 0.8, 0.8);
|
|
172
|
+
particles.colorDead = new BABYLON.Color4(0, 0, 0, 0);
|
|
173
|
+
|
|
174
|
+
particles.minSize = 0.02;
|
|
175
|
+
particles.maxSize = 0.08;
|
|
176
|
+
particles.minLifeTime = 2;
|
|
177
|
+
particles.maxLifeTime = 6;
|
|
178
|
+
particles.emitRate = 200;
|
|
179
|
+
particles.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD;
|
|
180
|
+
particles.gravity = new BABYLON.Vector3(0, -0.1, 0);
|
|
181
|
+
particles.minAngularSpeed = -Math.PI;
|
|
182
|
+
particles.maxAngularSpeed = Math.PI;
|
|
183
|
+
|
|
184
|
+
particles.start();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Solid Particle System (Performant Many-Mesh)
|
|
188
|
+
|
|
189
|
+
Use `SolidParticleSystem` when you need 3D geometry (not billboards) for thousands of instances:
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
const sps = new BABYLON.SolidParticleSystem("sps", scene);
|
|
193
|
+
const sphere = BABYLON.MeshBuilder.CreateSphere("tmp", { diameter: 0.1 }, scene);
|
|
194
|
+
sps.addShape(sphere, 500); // 500 sphere instances
|
|
195
|
+
sphere.dispose();
|
|
196
|
+
|
|
197
|
+
const mesh = sps.buildMesh();
|
|
198
|
+
|
|
199
|
+
// Position particles on init
|
|
200
|
+
sps.initParticles = () => {
|
|
201
|
+
for (let i = 0; i < sps.nbParticles; i++) {
|
|
202
|
+
const p = sps.particles[i];
|
|
203
|
+
p.position.x = (Math.random() - 0.5) * 10;
|
|
204
|
+
p.position.y = (Math.random() - 0.5) * 10;
|
|
205
|
+
p.position.z = (Math.random() - 0.5) * 10;
|
|
206
|
+
p.color = new BABYLON.Color4(Math.random(), Math.random(), Math.random(), 1);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
sps.initParticles();
|
|
211
|
+
sps.setParticles();
|
|
212
|
+
|
|
213
|
+
// Animate each frame
|
|
214
|
+
scene.onBeforeRenderObservable.add(() => {
|
|
215
|
+
const t = performance.now() / 1000;
|
|
216
|
+
for (let i = 0; i < sps.nbParticles; i++) {
|
|
217
|
+
const p = sps.particles[i];
|
|
218
|
+
p.rotation.y += 0.01;
|
|
219
|
+
p.position.y += Math.sin(t + i * 0.1) * 0.001;
|
|
220
|
+
}
|
|
221
|
+
sps.setParticles();
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Procedural Geometry
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
// Primitives via MeshBuilder
|
|
229
|
+
const box = BABYLON.MeshBuilder.CreateBox("box", { size: 1 }, scene);
|
|
230
|
+
const sphere = BABYLON.MeshBuilder.CreateSphere("s", { diameter: 1, segments: 32 }, scene);
|
|
231
|
+
const torus = BABYLON.MeshBuilder.CreateTorus("t", { diameter: 2, thickness: 0.3 }, scene);
|
|
232
|
+
const ribbon = BABYLON.MeshBuilder.CreateRibbon("r", { pathArray: paths }, scene);
|
|
233
|
+
const tube = BABYLON.MeshBuilder.CreateTube("tube", { path: points, radius: 0.1 }, scene);
|
|
234
|
+
|
|
235
|
+
// Custom vertex data
|
|
236
|
+
const positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]);
|
|
237
|
+
const indices = new Uint32Array([0, 1, 2]);
|
|
238
|
+
const normals = [];
|
|
239
|
+
const vertexData = new BABYLON.VertexData();
|
|
240
|
+
vertexData.positions = positions;
|
|
241
|
+
vertexData.indices = indices;
|
|
242
|
+
BABYLON.VertexData.ComputeNormals(positions, indices, normals);
|
|
243
|
+
vertexData.normals = normals;
|
|
244
|
+
const customMesh = new BABYLON.Mesh("custom", scene);
|
|
245
|
+
vertexData.applyToMesh(customMesh);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Custom GLSL Shaders
|
|
249
|
+
|
|
250
|
+
```javascript
|
|
251
|
+
BABYLON.Effect.ShadersStore["colorCycleVertexShader"] = `
|
|
252
|
+
precision highp float;
|
|
253
|
+
attribute vec3 position;
|
|
254
|
+
attribute vec2 uv;
|
|
255
|
+
uniform mat4 worldViewProjection;
|
|
256
|
+
varying vec2 vUv;
|
|
257
|
+
void main() {
|
|
258
|
+
vUv = uv;
|
|
259
|
+
gl_Position = worldViewProjection * vec4(position, 1.0);
|
|
260
|
+
}
|
|
261
|
+
`;
|
|
262
|
+
|
|
263
|
+
BABYLON.Effect.ShadersStore["colorCycleFragmentShader"] = `
|
|
264
|
+
precision highp float;
|
|
265
|
+
varying vec2 vUv;
|
|
266
|
+
uniform float time;
|
|
267
|
+
uniform float hueShift;
|
|
268
|
+
vec3 hsl2rgb(float h, float s, float l) {
|
|
269
|
+
float c = (1.0 - abs(2.0 * l - 1.0)) * s;
|
|
270
|
+
float x = c * (1.0 - abs(mod(h / 60.0, 2.0) - 1.0));
|
|
271
|
+
float m = l - c / 2.0;
|
|
272
|
+
vec3 rgb;
|
|
273
|
+
if (h < 60.0) rgb = vec3(c, x, 0.0);
|
|
274
|
+
else if (h < 120.0) rgb = vec3(x, c, 0.0);
|
|
275
|
+
else if (h < 180.0) rgb = vec3(0.0, c, x);
|
|
276
|
+
else if (h < 240.0) rgb = vec3(0.0, x, c);
|
|
277
|
+
else if (h < 300.0) rgb = vec3(x, 0.0, c);
|
|
278
|
+
else rgb = vec3(c, 0.0, x);
|
|
279
|
+
return rgb + m;
|
|
280
|
+
}
|
|
281
|
+
void main() {
|
|
282
|
+
float hue = mod(hueShift + vUv.x * 120.0 + time * 30.0, 360.0);
|
|
283
|
+
vec3 color = hsl2rgb(hue, 0.8, 0.5 + sin(time + vUv.y * 6.28) * 0.15);
|
|
284
|
+
gl_FragColor = vec4(color, 1.0);
|
|
285
|
+
}
|
|
286
|
+
`;
|
|
287
|
+
|
|
288
|
+
const shaderMat = new BABYLON.ShaderMaterial("colorCycle", scene, "colorCycle", {
|
|
289
|
+
attributes: ["position", "uv"],
|
|
290
|
+
uniforms: ["worldViewProjection", "time", "hueShift"],
|
|
291
|
+
});
|
|
292
|
+
shaderMat.setFloat("hueShift", 0);
|
|
293
|
+
|
|
294
|
+
// Update uniforms each frame
|
|
295
|
+
const startTime = performance.now();
|
|
296
|
+
scene.onBeforeRenderObservable.add(() => {
|
|
297
|
+
shaderMat.setFloat("time", (performance.now() - startTime) / 1000);
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Controller & Hand Tracking
|
|
304
|
+
|
|
305
|
+
### Motion Controllers
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
const xrInput = xrHelper.input;
|
|
309
|
+
|
|
310
|
+
xrInput.onControllerAddedObservable.add((controller) => {
|
|
311
|
+
controller.onMotionControllerInitObservable.add((motionController) => {
|
|
312
|
+
const triggerComponent = motionController.getComponent(BABYLON.WebXRControllerComponent.TRIGGER_TYPE);
|
|
313
|
+
if (triggerComponent) {
|
|
314
|
+
triggerComponent.onButtonStateChangedObservable.add((component) => {
|
|
315
|
+
if (component.pressed) {
|
|
316
|
+
console.log("Trigger pressed on", motionController.handedness);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Controller mesh position/rotation
|
|
322
|
+
const grip = controller.grip; // the physical grip space
|
|
323
|
+
// grip.position, grip.rotationQuaternion update every frame
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Hand Tracking
|
|
329
|
+
|
|
330
|
+
```javascript
|
|
331
|
+
const handTracking = xrHelper.featuresManager.enableFeature(BABYLON.WebXRHandTracking, "latest", { xrInput: xrHelper.input });
|
|
332
|
+
|
|
333
|
+
handTracking.onHandAddedObservable.add((hand) => {
|
|
334
|
+
// Get index fingertip position every frame
|
|
335
|
+
scene.onBeforeRenderObservable.add(() => {
|
|
336
|
+
const tip = hand.getJointMesh(BABYLON.WebXRHandJoint.INDEX_FINGER_TIP);
|
|
337
|
+
if (tip) {
|
|
338
|
+
// tip.position is the world-space fingertip position
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Materials & PBR
|
|
347
|
+
|
|
348
|
+
```javascript
|
|
349
|
+
// Standard PBR material
|
|
350
|
+
const pbr = new BABYLON.PBRMaterial("pbr", scene);
|
|
351
|
+
pbr.albedoColor = new BABYLON.Color3(0.2, 0.6, 1.0);
|
|
352
|
+
pbr.metallic = 0.8;
|
|
353
|
+
pbr.roughness = 0.2;
|
|
354
|
+
|
|
355
|
+
// Emissive glow
|
|
356
|
+
pbr.emissiveColor = new BABYLON.Color3(0.1, 0.4, 0.9);
|
|
357
|
+
pbr.emissiveIntensity = 1.5;
|
|
358
|
+
|
|
359
|
+
// Glass
|
|
360
|
+
const glass = new BABYLON.PBRMaterial("glass", scene);
|
|
361
|
+
glass.alpha = 0.3;
|
|
362
|
+
glass.metallic = 0;
|
|
363
|
+
glass.roughness = 0;
|
|
364
|
+
glass.subSurface.isRefractionEnabled = true;
|
|
365
|
+
glass.subSurface.refractionIntensity = 0.8;
|
|
366
|
+
|
|
367
|
+
// Unlit (good for AR overlays — no lighting calculation)
|
|
368
|
+
const unlit = new BABYLON.StandardMaterial("unlit", scene);
|
|
369
|
+
unlit.emissiveColor = new BABYLON.Color3(1, 0.5, 0);
|
|
370
|
+
unlit.disableLighting = true;
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Performance in VR
|
|
376
|
+
|
|
377
|
+
- Target **72 fps on Quest** — keep draw calls under ~100 per frame
|
|
378
|
+
- Use `mesh.freezeWorldMatrix()` for any mesh that never moves
|
|
379
|
+
- Use `scene.freezeMaterials()` after all materials are set up
|
|
380
|
+
- Prefer `SolidParticleSystem` over individual meshes for 100+ repeated objects
|
|
381
|
+
- Use `BABYLON.InstancedMesh` for identical geometry:
|
|
382
|
+
|
|
383
|
+
```javascript
|
|
384
|
+
const source = BABYLON.MeshBuilder.CreateSphere("source", { diameter: 0.1 }, scene);
|
|
385
|
+
source.isVisible = false;
|
|
386
|
+
|
|
387
|
+
for (let i = 0; i < 200; i++) {
|
|
388
|
+
const instance = source.createInstance(`orb_${i}`);
|
|
389
|
+
instance.position.set((Math.random() - 0.5) * 10, (Math.random() - 0.5) * 10, (Math.random() - 0.5) * 10);
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Common Gotchas
|
|
396
|
+
|
|
397
|
+
- **Import from `@babylonjs/core`, not `babylonjs`** — the legacy `babylonjs` package times out on esm.sh; the scoped package resolves correctly. `import * as BABYLON from "@babylonjs/core"` gives the same `BABYLON.*` namespace.
|
|
398
|
+
- **`createDefaultXRExperienceAsync` is async** — always `await` it or chain `.then()`. Calling it without await silently skips XR setup.
|
|
399
|
+
- **AR requires HTTPS** — `localhost` works for dev; plain `http://` will fail on device.
|
|
400
|
+
- **Quest AR setting** — `immersive-ar` may require "WebXR Passthrough" enabled in Quest Browser settings (about://flags).
|
|
401
|
+
- **Apple Vision Pro** — `immersive-vr` works via visionOS Safari WebXR; AR passthrough is not yet available.
|
|
402
|
+
- **Always call `engine.resize()`** on window resize AND when XR display geometry changes.
|
|
403
|
+
- **`rotationQuaternion` vs `rotation`** — once Babylon sets `rotationQuaternion` on a mesh (e.g. via XR tracking), `rotation` is ignored. Use `rotationQuaternion` consistently.
|
|
404
|
+
- **Dispose properly** — call `scene.dispose()` and `engine.dispose()` on React component unmount to prevent memory leaks.
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Real-World Example 1: VR Generative Art — Particle Galaxy
|
|
409
|
+
|
|
410
|
+
A floating galaxy of 2000 particles with a custom-shader core sphere, navigable in VR. Session data stored in Fireproof.
|
|
411
|
+
|
|
412
|
+
```javascript
|
|
413
|
+
import * as BABYLON from "@babylonjs/core";
|
|
414
|
+
import React, { useEffect, useRef } from "react";
|
|
415
|
+
import { useFireproof } from "use-fireproof";
|
|
416
|
+
|
|
417
|
+
// ── Pure Babylon functions (no React, no Fireproof) ────────────────────────
|
|
418
|
+
|
|
419
|
+
function buildScene(canvas) {
|
|
420
|
+
const engine = new BABYLON.Engine(canvas, true);
|
|
421
|
+
const scene = new BABYLON.Scene(engine);
|
|
422
|
+
scene.clearColor = new BABYLON.Color4(0, 0, 0.02, 1);
|
|
423
|
+
|
|
424
|
+
const camera = new BABYLON.FreeCamera("cam", new BABYLON.Vector3(0, 1.7, -8), scene);
|
|
425
|
+
camera.setTarget(BABYLON.Vector3.Zero());
|
|
426
|
+
camera.attachControl(canvas, true);
|
|
427
|
+
|
|
428
|
+
new BABYLON.HemisphericLight("amb", new BABYLON.Vector3(0, 1, 0), scene).intensity = 0.1;
|
|
429
|
+
|
|
430
|
+
engine.runRenderLoop(() => scene.render());
|
|
431
|
+
return { engine, scene };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function buildGalaxy(scene) {
|
|
435
|
+
const galaxy = new BABYLON.ParticleSystem("galaxy", 2000, scene);
|
|
436
|
+
galaxy.particleTexture = new BABYLON.Texture("https://playground.babylonjs.com/textures/flare.png", scene);
|
|
437
|
+
galaxy.emitter = new BABYLON.Vector3(0, 0, 0);
|
|
438
|
+
galaxy.minEmitBox = new BABYLON.Vector3(-6, -0.5, -6);
|
|
439
|
+
galaxy.maxEmitBox = new BABYLON.Vector3(6, 0.5, 6);
|
|
440
|
+
galaxy.color1 = new BABYLON.Color4(0.4, 0.7, 1, 1);
|
|
441
|
+
galaxy.color2 = new BABYLON.Color4(1, 0.4, 0.8, 0.6);
|
|
442
|
+
galaxy.colorDead = new BABYLON.Color4(0, 0, 0, 0);
|
|
443
|
+
galaxy.minSize = 0.03;
|
|
444
|
+
galaxy.maxSize = 0.12;
|
|
445
|
+
galaxy.minLifeTime = 4;
|
|
446
|
+
galaxy.maxLifeTime = 10;
|
|
447
|
+
galaxy.emitRate = 80;
|
|
448
|
+
galaxy.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD;
|
|
449
|
+
galaxy.gravity = new BABYLON.Vector3(0, 0, 0);
|
|
450
|
+
galaxy.minAngularSpeed = -0.5;
|
|
451
|
+
galaxy.maxAngularSpeed = 0.5;
|
|
452
|
+
galaxy.start();
|
|
453
|
+
return galaxy;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function buildCoreShader(scene) {
|
|
457
|
+
if (!BABYLON.Effect.ShadersStore["galaxyCoreVertexShader"]) {
|
|
458
|
+
BABYLON.Effect.ShadersStore["galaxyCoreVertexShader"] = `
|
|
459
|
+
precision highp float;
|
|
460
|
+
attribute vec3 position; attribute vec2 uv;
|
|
461
|
+
uniform mat4 worldViewProjection;
|
|
462
|
+
varying vec2 vUv;
|
|
463
|
+
void main() { vUv = uv; gl_Position = worldViewProjection * vec4(position, 1.0); }
|
|
464
|
+
`;
|
|
465
|
+
BABYLON.Effect.ShadersStore["galaxyCoreFragmentShader"] = `
|
|
466
|
+
precision highp float;
|
|
467
|
+
varying vec2 vUv; uniform float time;
|
|
468
|
+
void main() {
|
|
469
|
+
vec2 p = vUv - 0.5;
|
|
470
|
+
float r = length(p);
|
|
471
|
+
float glow = 0.05 / max(r, 0.01);
|
|
472
|
+
float hue = mod(atan(p.y, p.x) / 6.283 + time * 0.1, 1.0) * 360.0;
|
|
473
|
+
float c = mod(hue / 60.0, 2.0) - 1.0;
|
|
474
|
+
vec3 col = vec3(
|
|
475
|
+
step(hue, 60.0) * (1.0 - abs(c)) + step(300.0, hue),
|
|
476
|
+
step(60.0, hue) * step(hue, 180.0) * (1.0 - abs(mod(hue / 60.0, 2.0) - 1.0)),
|
|
477
|
+
step(180.0, hue) * step(hue, 300.0) * (1.0 - abs(mod(hue / 60.0, 2.0) - 1.0))
|
|
478
|
+
);
|
|
479
|
+
gl_FragColor = vec4(col * glow * 2.0, min(glow, 1.0));
|
|
480
|
+
}
|
|
481
|
+
`;
|
|
482
|
+
}
|
|
483
|
+
const mat = new BABYLON.ShaderMaterial("galaxyCore", scene, "galaxyCore", {
|
|
484
|
+
attributes: ["position", "uv"],
|
|
485
|
+
uniforms: ["worldViewProjection", "time"],
|
|
486
|
+
});
|
|
487
|
+
mat.backFaceCulling = false;
|
|
488
|
+
mat.alphaMode = BABYLON.Constants.ALPHA_ADD;
|
|
489
|
+
|
|
490
|
+
const sphere = BABYLON.MeshBuilder.CreateSphere("core", { diameter: 1.5, segments: 32 }, scene);
|
|
491
|
+
sphere.material = mat;
|
|
492
|
+
|
|
493
|
+
const t0 = performance.now();
|
|
494
|
+
scene.onBeforeRenderObservable.add(() => {
|
|
495
|
+
mat.setFloat("time", (performance.now() - t0) / 1000);
|
|
496
|
+
});
|
|
497
|
+
return sphere;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async function enableVR(scene) {
|
|
501
|
+
const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 20, height: 20 }, scene);
|
|
502
|
+
ground.isVisible = false;
|
|
503
|
+
return scene.createDefaultXRExperienceAsync({ floorMeshes: [ground] });
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ── React container component ──────────────────────────────────────────────
|
|
507
|
+
|
|
508
|
+
export default function App() {
|
|
509
|
+
const { database, useLiveQuery } = useFireproof("galaxy-sessions");
|
|
510
|
+
const canvasRef = useRef(null);
|
|
511
|
+
const { docs: sessions } = useLiveQuery("type", { key: "session" });
|
|
512
|
+
|
|
513
|
+
useEffect(() => {
|
|
514
|
+
if (!canvasRef.current) return;
|
|
515
|
+
const { engine, scene } = buildScene(canvasRef.current);
|
|
516
|
+
const onResize = () => engine.resize();
|
|
517
|
+
window.addEventListener("resize", onResize);
|
|
518
|
+
buildGalaxy(scene);
|
|
519
|
+
buildCoreShader(scene);
|
|
520
|
+
enableVR(scene).then(() => {
|
|
521
|
+
database.put({ type: "session", startedAt: Date.now() });
|
|
522
|
+
});
|
|
523
|
+
return () => {
|
|
524
|
+
window.removeEventListener("resize", onResize);
|
|
525
|
+
scene.dispose();
|
|
526
|
+
engine.dispose();
|
|
527
|
+
};
|
|
528
|
+
}, []);
|
|
529
|
+
|
|
530
|
+
return (
|
|
531
|
+
<div className="relative w-full h-screen bg-black">
|
|
532
|
+
<canvas ref={canvasRef} className="w-full h-full" />
|
|
533
|
+
<div className="absolute top-4 left-4 text-white text-sm opacity-70">Sessions: {sessions.length}</div>
|
|
534
|
+
</div>
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## Real-World Example 2: AR Passthrough — Tap to Place Glowing Orbs
|
|
542
|
+
|
|
543
|
+
Tap a real-world surface to plant a pulsing orb anchored in place. Orb positions are stored in Fireproof so they survive page reload.
|
|
544
|
+
|
|
545
|
+
On devices without AR support (desktop, unsupported browsers) the app automatically enters **fixture mode**: a background photo stands in for the passthrough feed, and mouse clicks place orbs via ray-picking. The 3D scene is identical in both modes — only the background source differs.
|
|
546
|
+
|
|
547
|
+
```javascript
|
|
548
|
+
import * as BABYLON from "@babylonjs/core";
|
|
549
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
550
|
+
import { useFireproof } from "use-fireproof";
|
|
551
|
+
|
|
552
|
+
const FIXTURE_BG = "https://picsum.photos/seed/indoor-room/1456/816";
|
|
553
|
+
|
|
554
|
+
// ── Pure Babylon functions ─────────────────────────────────────────────────
|
|
555
|
+
|
|
556
|
+
// Always alpha:true so orbs composite over AR passthrough or fixture img
|
|
557
|
+
function buildScene(canvas) {
|
|
558
|
+
const engine = new BABYLON.Engine(canvas, true, { alpha: true });
|
|
559
|
+
const scene = new BABYLON.Scene(engine);
|
|
560
|
+
scene.clearColor = new BABYLON.Color4(0, 0, 0, 0);
|
|
561
|
+
|
|
562
|
+
const camera = new BABYLON.FreeCamera("cam", new BABYLON.Vector3(0, 1.6, 0), scene);
|
|
563
|
+
camera.minZ = 0.01;
|
|
564
|
+
camera.setTarget(new BABYLON.Vector3(0, 1.6, 3));
|
|
565
|
+
|
|
566
|
+
engine.runRenderLoop(() => scene.render());
|
|
567
|
+
return { engine, scene, camera };
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function makeOrbMaterial(scene) {
|
|
571
|
+
if (!BABYLON.Effect.ShadersStore["orbVertexShader"]) {
|
|
572
|
+
BABYLON.Effect.ShadersStore["orbVertexShader"] = `
|
|
573
|
+
precision highp float;
|
|
574
|
+
attribute vec3 position; attribute vec2 uv;
|
|
575
|
+
uniform mat4 worldViewProjection;
|
|
576
|
+
varying vec2 vUv;
|
|
577
|
+
void main() { vUv = uv; gl_Position = worldViewProjection * vec4(position, 1.0); }
|
|
578
|
+
`;
|
|
579
|
+
BABYLON.Effect.ShadersStore["orbFragmentShader"] = `
|
|
580
|
+
precision highp float;
|
|
581
|
+
varying vec2 vUv; uniform float time; uniform vec3 baseColor;
|
|
582
|
+
void main() {
|
|
583
|
+
vec2 p = vUv - 0.5;
|
|
584
|
+
float r = length(p);
|
|
585
|
+
float rim = smoothstep(0.5, 0.35, r) - smoothstep(0.35, 0.2, r);
|
|
586
|
+
float core = smoothstep(0.2, 0.0, r);
|
|
587
|
+
float pulse = 0.7 + 0.3 * sin(time * 3.0);
|
|
588
|
+
vec3 col = baseColor * (rim * 0.6 + core * 2.0) * pulse;
|
|
589
|
+
gl_FragColor = vec4(col, (rim + core) * 0.9);
|
|
590
|
+
}
|
|
591
|
+
`;
|
|
592
|
+
}
|
|
593
|
+
const mat = new BABYLON.ShaderMaterial("orb", scene, "orb", {
|
|
594
|
+
attributes: ["position", "uv"],
|
|
595
|
+
uniforms: ["worldViewProjection", "time", "baseColor"],
|
|
596
|
+
});
|
|
597
|
+
mat.backFaceCulling = false;
|
|
598
|
+
mat.alphaMode = BABYLON.Constants.ALPHA_ADD;
|
|
599
|
+
return mat;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function spawnOrb(scene, position, orbMat) {
|
|
603
|
+
const mesh = BABYLON.MeshBuilder.CreateSphere("orb", { diameter: 0.15, segments: 16 }, scene);
|
|
604
|
+
mesh.position.copyFrom(position);
|
|
605
|
+
const mat = orbMat.clone("orbInst_" + Date.now());
|
|
606
|
+
const hue = Math.random();
|
|
607
|
+
mat.setVector3("baseColor", new BABYLON.Vector3(hue, 0.5, 1 - hue));
|
|
608
|
+
mesh.material = mat;
|
|
609
|
+
return mesh;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Fixture mode: ray-pick through mouse click, place orb 3m along ray
|
|
613
|
+
function enableFixtureClicks(scene, camera, onPlace) {
|
|
614
|
+
scene.onPointerObservable.add((pointerInfo) => {
|
|
615
|
+
if (pointerInfo.type !== BABYLON.PointerEventTypes.POINTERDOWN) return;
|
|
616
|
+
const ray = scene.createPickingRay(scene.pointerX, scene.pointerY, BABYLON.Matrix.Identity(), camera);
|
|
617
|
+
onPlace(ray.origin.add(ray.direction.scale(3)));
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
async function enableAR(scene, onPlace) {
|
|
622
|
+
const xrHelper = await scene.createDefaultXRExperienceAsync({
|
|
623
|
+
uiOptions: { sessionMode: "immersive-ar", referenceSpaceType: "local-floor" },
|
|
624
|
+
optionalFeatures: true,
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
const hitTest = xrHelper.featuresManager.enableFeature(BABYLON.WebXRHitTest, "latest");
|
|
628
|
+
|
|
629
|
+
const indicator = BABYLON.MeshBuilder.CreateTorus("indicator", { diameter: 0.25, thickness: 0.008, tessellation: 32 }, scene);
|
|
630
|
+
const indicatorMat = new BABYLON.StandardMaterial("indMat", scene);
|
|
631
|
+
indicatorMat.emissiveColor = new BABYLON.Color3(0.4, 1, 0.8);
|
|
632
|
+
indicatorMat.disableLighting = true;
|
|
633
|
+
indicator.material = indicatorMat;
|
|
634
|
+
indicator.isVisible = false;
|
|
635
|
+
|
|
636
|
+
let latestHit = null;
|
|
637
|
+
hitTest.onHitTestResultObservable.add((results) => {
|
|
638
|
+
if (results.length > 0) {
|
|
639
|
+
indicator.isVisible = true;
|
|
640
|
+
latestHit = results[0];
|
|
641
|
+
results[0].transformationMatrix.decompose(
|
|
642
|
+
undefined,
|
|
643
|
+
indicator.rotationQuaternion || (indicator.rotationQuaternion = new BABYLON.Quaternion()),
|
|
644
|
+
indicator.position
|
|
645
|
+
);
|
|
646
|
+
} else {
|
|
647
|
+
indicator.isVisible = false;
|
|
648
|
+
latestHit = null;
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
scene.onPointerObservable.add((pointerInfo) => {
|
|
653
|
+
if (pointerInfo.type === BABYLON.PointerEventTypes.POINTERDOWN && latestHit) {
|
|
654
|
+
onPlace(indicator.position.clone());
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
return xrHelper;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// ── React container ────────────────────────────────────────────────────────
|
|
662
|
+
|
|
663
|
+
export default function App() {
|
|
664
|
+
const { database, useLiveQuery } = useFireproof("ar-orbs");
|
|
665
|
+
const canvasRef = useRef(null);
|
|
666
|
+
const sceneRef = useRef(null);
|
|
667
|
+
const orbMatRef = useRef(null);
|
|
668
|
+
const spawnedIdsRef = useRef(new Set());
|
|
669
|
+
const [orbCount, setOrbCount] = useState(0);
|
|
670
|
+
const [mode, setMode] = useState("checking"); // checking | ar | fixture
|
|
671
|
+
const [arError, setArError] = useState(null);
|
|
672
|
+
const { docs: savedOrbs } = useLiveQuery("type", { key: "orb" });
|
|
673
|
+
|
|
674
|
+
useEffect(() => {
|
|
675
|
+
if (!canvasRef.current) return;
|
|
676
|
+
let engine, scene, camera;
|
|
677
|
+
|
|
678
|
+
const onResize = () => engine?.resize();
|
|
679
|
+
window.addEventListener("resize", onResize);
|
|
680
|
+
|
|
681
|
+
async function init() {
|
|
682
|
+
const arSupported = await navigator.xr?.isSessionSupported("immersive-ar").catch(() => false);
|
|
683
|
+
({ engine, scene, camera } = buildScene(canvasRef.current));
|
|
684
|
+
sceneRef.current = scene;
|
|
685
|
+
orbMatRef.current = makeOrbMaterial(scene);
|
|
686
|
+
|
|
687
|
+
const t0 = performance.now();
|
|
688
|
+
scene.onBeforeRenderObservable.add(() => {
|
|
689
|
+
const t = (performance.now() - t0) / 1000;
|
|
690
|
+
scene.meshes.forEach((m) => {
|
|
691
|
+
if (m.material?.getClassName?.() === "ShaderMaterial" && m.name.startsWith("orb")) {
|
|
692
|
+
m.material.setFloat("time", t);
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
const handlePlace = async (pos) => {
|
|
698
|
+
spawnOrb(scene, pos, orbMatRef.current);
|
|
699
|
+
setOrbCount((n) => n + 1);
|
|
700
|
+
const { id } = await database.put({ type: "orb", x: pos.x, y: pos.y, z: pos.z, ts: Date.now() });
|
|
701
|
+
spawnedIdsRef.current.add(id);
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
if (arSupported) {
|
|
705
|
+
setMode("ar");
|
|
706
|
+
try {
|
|
707
|
+
await enableAR(scene, handlePlace);
|
|
708
|
+
} catch (e) {
|
|
709
|
+
setArError(e?.message ?? String(e));
|
|
710
|
+
}
|
|
711
|
+
} else {
|
|
712
|
+
setMode("fixture");
|
|
713
|
+
enableFixtureClicks(scene, camera, handlePlace);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
init();
|
|
718
|
+
return () => {
|
|
719
|
+
window.removeEventListener("resize", onResize);
|
|
720
|
+
scene?.dispose();
|
|
721
|
+
engine?.dispose();
|
|
722
|
+
};
|
|
723
|
+
}, []);
|
|
724
|
+
|
|
725
|
+
useEffect(() => {
|
|
726
|
+
if (!sceneRef.current || !orbMatRef.current || savedOrbs.length === 0) return;
|
|
727
|
+
savedOrbs.forEach((doc) => {
|
|
728
|
+
if (spawnedIdsRef.current.has(doc._id)) return;
|
|
729
|
+
spawnedIdsRef.current.add(doc._id);
|
|
730
|
+
spawnOrb(sceneRef.current, new BABYLON.Vector3(doc.x, doc.y, doc.z), orbMatRef.current);
|
|
731
|
+
});
|
|
732
|
+
}, [savedOrbs.length]);
|
|
733
|
+
|
|
734
|
+
return (
|
|
735
|
+
<div className="relative w-full h-screen overflow-hidden">
|
|
736
|
+
{mode === "fixture" && <img src={FIXTURE_BG} className="absolute inset-0 w-full h-full object-cover" alt="" />}
|
|
737
|
+
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full" style={{ background: "transparent" }} />
|
|
738
|
+
<div className="absolute top-4 left-4 bg-black/40 text-white px-3 py-2 rounded-lg text-sm">
|
|
739
|
+
{mode === "checking" && "Checking AR support…"}
|
|
740
|
+
{mode === "ar" && `Tap a surface to place an orb · ${orbCount} placed`}
|
|
741
|
+
{mode === "fixture" && `Desktop preview — click to place orbs · ${orbCount} placed`}
|
|
742
|
+
</div>
|
|
743
|
+
{arError && (
|
|
744
|
+
<div className="absolute bottom-4 left-4 right-4 bg-red-900/80 text-white px-3 py-2 rounded-lg text-sm">
|
|
745
|
+
AR error: {arError}
|
|
746
|
+
</div>
|
|
747
|
+
)}
|
|
748
|
+
</div>
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibes.diy/prompts",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.19",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"description": "",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"@fireproof/core-types-base": "~0.24.19",
|
|
31
31
|
"@fireproof/core-types-protocols-cloud": "~0.24.19",
|
|
32
32
|
"@fireproof/use-fireproof": "~0.24.19",
|
|
33
|
-
"@vibes.diy/call-ai-v2": "^2.2.
|
|
34
|
-
"@vibes.diy/use-vibes-types": "^2.2.
|
|
33
|
+
"@vibes.diy/call-ai-v2": "^2.2.19",
|
|
34
|
+
"@vibes.diy/use-vibes-types": "^2.2.19",
|
|
35
35
|
"arktype": "~2.2.0",
|
|
36
36
|
"json-schema-faker": "~0.6.1"
|
|
37
37
|
},
|
package/prompts.d.ts
CHANGED
|
@@ -38,11 +38,11 @@ export declare const preAllocSchema: {
|
|
|
38
38
|
};
|
|
39
39
|
readonly iconDescription: {
|
|
40
40
|
readonly type: "string";
|
|
41
|
-
readonly description: "A short, vivid one-line description of what an icon for this app should depict
|
|
41
|
+
readonly description: "A short, vivid one-line description of what an icon for this app should depict — what it shows, not how it's drawn. Examples: 'a fox on a record player', 'a sailboat on a calm lake', 'a chef whisking eggs'. Don't mention colors, line weights, letters, numbers, or framing — those are added separately by the renderer.";
|
|
42
42
|
};
|
|
43
43
|
readonly theme: {
|
|
44
44
|
readonly type: "string";
|
|
45
|
-
readonly description: "Theme slug from the theme catalog above. Pick the one whose mood best fits the app
|
|
45
|
+
readonly description: "Theme slug from the theme catalog above. Pick the one whose mood best fits the app — playful apps lean playful themes, focus/utility apps lean clean themes, retro apps lean retro themes, etc. Always pick something rather than leaving empty; the catalog is broad enough to cover most app moods. Only omit if the request is so abstract that every theme would feel arbitrary.";
|
|
46
46
|
};
|
|
47
47
|
readonly enrichedPrompt: {
|
|
48
48
|
readonly type: "string";
|
package/system-prompt.md
CHANGED
|
@@ -9,7 +9,7 @@ You are an AI assistant tasked with creating React components. You should create
|
|
|
9
9
|
- Avoid using external libraries unless they are essential for the component to function
|
|
10
10
|
- Always use ES module imports at the top of the file (e.g. `import React, { useState } from "react"`). Never reference React or other libraries as globals.
|
|
11
11
|
- Your file MUST use `export default function App()` — the runtime loads it as an ES module and imports the default export.
|
|
12
|
-
- Structure your component code in this order: (1) hooks and document shapes, (2) event handlers, (3) classNames object, (4) JSX return. ClassNames go right before JSX so they are close to where they are used.
|
|
12
|
+
- Structure your component code in this order: (1) hooks and document shapes, (2) event handlers, (3) classNames object, (4) JSX return. ClassNames go right before JSX so they are close to where they are used. Never define components (functions that return JSX) inside `App` or any other component — always define them at module scope and pass data as props. Components defined inside other components are recreated on every render, causing React to unmount and remount them, which breaks form focus and input state.
|
|
13
13
|
- Use Fireproof for data persistence
|
|
14
14
|
- Use `callAI` to fetch AI, use schema like this: `JSON.parse(await callAI(prompt, { schema: { properties: { todos: { type: 'array', items: { type: 'string' } } } } }))` and save final responses as individual Fireproof documents.
|
|
15
15
|
- Always show loading states during any async operation (callAI, fetch, database queries): use a useState boolean (e.g. `isLoading`), set it true before the call and false in .finally(). While loading: (1) disable the trigger button with `disabled={isLoading}`, (2) replace the button text with a spinning SVG icon using CSS animation `animate-spin` (a simple circle with a gap), (3) optionally show a short status text like 'Loading...' near the button. Never leave the user clicking a button with no visual feedback. Pattern: `setIsLoading(true); try { await callAI(...); } finally { setIsLoading(false); }`
|