voidcore 0.0.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/README.md +303 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.js +1162 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# V1V2 Engine
|
|
2
|
+
|
|
3
|
+
Engine in development for [Mana Blade](https://manablade.com/).
|
|
4
|
+
|
|
5
|
+
A minimal, high-performance WebGPU/WebGL rendering engine with a Three.js-like API.
|
|
6
|
+
|
|
7
|
+
## Quickstart
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { createScene, Mesh, OrbitControls, Scheduler, cubeVertices, cubeIndices } from '@v1v2/engine'
|
|
11
|
+
|
|
12
|
+
const canvas = document.querySelector('canvas')!
|
|
13
|
+
const scene = await createScene(canvas)
|
|
14
|
+
|
|
15
|
+
// Register geometry
|
|
16
|
+
const cubeGeo = scene.registerGeometry(cubeVertices, cubeIndices)
|
|
17
|
+
|
|
18
|
+
// Add a mesh
|
|
19
|
+
const cube = scene.add(
|
|
20
|
+
new Mesh({
|
|
21
|
+
geometry: cubeGeo,
|
|
22
|
+
position: [0, 0, 0],
|
|
23
|
+
color: [1, 0.3, 0.3],
|
|
24
|
+
}),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
// Camera
|
|
28
|
+
const orbit = new OrbitControls(canvas)
|
|
29
|
+
scene.camera.fov = Math.PI / 3
|
|
30
|
+
scene.camera.near = 0.1
|
|
31
|
+
scene.camera.far = 1000
|
|
32
|
+
|
|
33
|
+
// Lighting
|
|
34
|
+
scene.setDirectionalLight([-1, -1, -2], [0.5, 0.5, 0.5])
|
|
35
|
+
scene.setAmbientLight([0.8, 0.8, 0.8])
|
|
36
|
+
|
|
37
|
+
// Shadows
|
|
38
|
+
scene.shadow.enabled = true
|
|
39
|
+
scene.shadow.target.set([0, 0, 0])
|
|
40
|
+
|
|
41
|
+
// Scheduler
|
|
42
|
+
const scheduler = new Scheduler(scene)
|
|
43
|
+
scheduler.maxFps = 60
|
|
44
|
+
|
|
45
|
+
scheduler.register(({ dt }) => {
|
|
46
|
+
cube.rotation[2]! += dt
|
|
47
|
+
scene.camera.eye.set(orbit.eye)
|
|
48
|
+
scene.camera.target.set(orbit.target)
|
|
49
|
+
scene.render()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
scheduler.start()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Features
|
|
56
|
+
|
|
57
|
+
- WebGPU with automatic WebGL2 fallback
|
|
58
|
+
- Structure-of-Arrays internals for cache-friendly rendering
|
|
59
|
+
- Frustum culling with bounding spheres
|
|
60
|
+
- Shadow mapping (directional light)
|
|
61
|
+
- Bloom post-processing (5-mip downsample/upsample chain)
|
|
62
|
+
- Outline rendering (MRT-based, configurable thickness/color)
|
|
63
|
+
- GPU skinning with animation crossfade blending
|
|
64
|
+
- Bone attachment system (parent meshes to skeleton bones)
|
|
65
|
+
- BVH-accelerated raycasting (SAH-binned, zero allocation in hot path)
|
|
66
|
+
- GLB/glTF loading with Draco mesh compression
|
|
67
|
+
- KTX2/Basis Universal texture loading
|
|
68
|
+
- HTML overlay (project DOM elements to 3D world positions)
|
|
69
|
+
- Orbit controls (pan, rotate, zoom)
|
|
70
|
+
- Transparent object sorting (back-to-front)
|
|
71
|
+
- Priority-based rAF scheduler with global and per-callback FPS throttling
|
|
72
|
+
- Zero per-frame allocations in the hot path
|
|
73
|
+
|
|
74
|
+
## API
|
|
75
|
+
|
|
76
|
+
### Scene
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const scene = await createScene(canvas, { maxEntities: 10_000, backend: 'webgpu' })
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Geometry
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const geo = scene.registerGeometry(vertices, indices)
|
|
86
|
+
const skinnedGeo = scene.registerSkinnedGeometry(vertices, indices, joints, weights)
|
|
87
|
+
const texturedGeo = scene.registerTexturedGeometry(vertices, indices, uvs)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Built-in Primitives
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { createBoxGeometry, createSphereGeometry, mergeGeometries } from '@v1v2/engine'
|
|
94
|
+
|
|
95
|
+
const box = createBoxGeometry(1, 1, 1)
|
|
96
|
+
const sphere = createSphereGeometry(0.5, 32, 16)
|
|
97
|
+
const merged = mergeGeometries([
|
|
98
|
+
{ vertices: box.vertices, indices: box.indices, color: [1, 0, 0] },
|
|
99
|
+
{ vertices: sphere.vertices, indices: sphere.indices, color: [0, 0, 1] },
|
|
100
|
+
])
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Meshes
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const mesh = scene.add(
|
|
107
|
+
new Mesh({
|
|
108
|
+
geometry: geo,
|
|
109
|
+
position: [0, 0, 0],
|
|
110
|
+
rotation: [0, 0, 0],
|
|
111
|
+
scale: [1, 1, 1],
|
|
112
|
+
color: [1, 1, 1],
|
|
113
|
+
alpha: 1,
|
|
114
|
+
unlit: false,
|
|
115
|
+
}),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
mesh.position[0]! += 1 // direct mutation
|
|
119
|
+
mesh.visible = false // hide
|
|
120
|
+
mesh.bloom = 1.5 // emissive glow (0 = off)
|
|
121
|
+
mesh.outline = 1 // outline group (0 = off)
|
|
122
|
+
scene.remove(mesh) // remove from scene
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Camera
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
scene.camera.fov = Math.PI / 3
|
|
129
|
+
scene.camera.near = 0.1
|
|
130
|
+
scene.camera.far = 5000
|
|
131
|
+
scene.camera.eye.set([0, -10, 5])
|
|
132
|
+
scene.camera.target.set([0, 0, 0])
|
|
133
|
+
scene.camera.up.set([0, 0, 1])
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Lighting
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
scene.setDirectionalLight([dx, dy, dz], [r, g, b])
|
|
140
|
+
scene.setAmbientLight([r, g, b])
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Shadows
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
scene.shadow.enabled = true
|
|
147
|
+
scene.shadow.target.set([0, 0, 0])
|
|
148
|
+
scene.shadow.distance = 400
|
|
149
|
+
scene.shadow.extent = 150
|
|
150
|
+
scene.shadow.near = 1
|
|
151
|
+
scene.shadow.far = 800
|
|
152
|
+
scene.shadow.bias = 0.0001
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Bloom
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
scene.bloom.enabled = true
|
|
159
|
+
scene.bloom.intensity = 1.0
|
|
160
|
+
scene.bloom.threshold = 0.0
|
|
161
|
+
scene.bloom.radius = 1.0
|
|
162
|
+
scene.bloom.whiten = 0.0
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Outline
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
scene.outline.enabled = true
|
|
169
|
+
scene.outline.thickness = 3
|
|
170
|
+
scene.outline.color = [1, 0.5, 0]
|
|
171
|
+
scene.outline.distanceFactor = 0 // distance-based scaling
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Raycasting
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { createRaycastHit } from '@v1v2/engine'
|
|
178
|
+
|
|
179
|
+
const hit = createRaycastHit() // reusable receiver — no per-frame allocation
|
|
180
|
+
scene.buildBVH(groundMesh.geometry) // optional pre-build (lazy-built on first raycast otherwise)
|
|
181
|
+
|
|
182
|
+
if (scene.raycast(x, y, z + 50, 0, 0, -1, hit, [groundMesh])) {
|
|
183
|
+
player.position[2] = hit.pointZ
|
|
184
|
+
// hit.distance, hit.normalX/Y/Z, hit.faceIndex, hit.mesh
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### GLB/glTF Loading
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { loadGlb } from '@v1v2/engine'
|
|
192
|
+
|
|
193
|
+
const glb = await loadGlb('/model.glb', '/draco-1.5.7/')
|
|
194
|
+
// glb.meshes, glb.skins, glb.animations, glb.nodeTransforms
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Skinning & Animation
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { createSkeleton, createSkinInstance, updateSkinInstance, transitionTo } from '@v1v2/engine'
|
|
201
|
+
|
|
202
|
+
const skeleton = createSkeleton(glb.skins[0], glb.nodeTransforms)
|
|
203
|
+
const skin = createSkinInstance(skeleton, 0) // start with clip 0
|
|
204
|
+
scene.skinInstances.push(skin)
|
|
205
|
+
|
|
206
|
+
// In render loop:
|
|
207
|
+
updateSkinInstance(skin, glb.animations, dt)
|
|
208
|
+
|
|
209
|
+
// Crossfade to a new clip:
|
|
210
|
+
transitionTo(skin, 2, 0.2) // blend to clip 2 over 0.2s
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Bone Attachment
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
scene.attachToBone(weaponMesh, characterMesh, 'Hand.R')
|
|
217
|
+
scene.detachFromBone(weaponMesh)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Textures
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { loadKTX2 } from '@v1v2/engine'
|
|
224
|
+
|
|
225
|
+
const tex = await loadKTX2('/texture.ktx2', '/basis/')
|
|
226
|
+
const texId = scene.registerTexture(tex.data, tex.width, tex.height)
|
|
227
|
+
const geo = scene.registerTexturedGeometry(vertices, indices, uvs)
|
|
228
|
+
const mesh = scene.add(new Mesh({ geometry: geo, aoMap: texId }))
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### HTML Overlay
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import { HtmlOverlay, HtmlElement } from '@v1v2/engine'
|
|
235
|
+
|
|
236
|
+
const overlay = new HtmlOverlay(containerDiv)
|
|
237
|
+
const label = overlay.add(new HtmlElement({ position: [0, 0, 5], element: myDiv, distanceFactor: 1 }))
|
|
238
|
+
label.mesh = someMesh // track a mesh
|
|
239
|
+
|
|
240
|
+
// In render loop:
|
|
241
|
+
overlay.update(scene)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Rendering
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
scene.render() // sync + draw in one call
|
|
248
|
+
scene.resize(width, height) // resize canvas/backbuffer
|
|
249
|
+
scene.drawCalls // frame draw call count
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Scheduler
|
|
253
|
+
|
|
254
|
+
Priority-based rAF loop with optional per-callback and global FPS throttling.
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import { Scheduler } from '@v1v2/engine'
|
|
258
|
+
|
|
259
|
+
const scheduler = new Scheduler(scene)
|
|
260
|
+
|
|
261
|
+
// Global FPS cap — throttles the entire loop (0 = uncapped)
|
|
262
|
+
scheduler.maxFps = 60
|
|
263
|
+
|
|
264
|
+
// Register callbacks with priority ordering (lower = earlier)
|
|
265
|
+
const unsub = scheduler.register(
|
|
266
|
+
({ scene, dt, elapsed, frame }) => {
|
|
267
|
+
// dt = seconds since last tick (capped at 0.1s)
|
|
268
|
+
player.position[1]! += speed * dt
|
|
269
|
+
},
|
|
270
|
+
{ priority: -1 },
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
// Per-callback FPS throttle (independent of global cap)
|
|
274
|
+
scheduler.register(({ scene }) => scene.render(), { priority: 0, fps: 30 })
|
|
275
|
+
|
|
276
|
+
scheduler.start()
|
|
277
|
+
scheduler.stop() // pause (resumable)
|
|
278
|
+
unsub() // remove a callback
|
|
279
|
+
scheduler.destroy() // stop + remove all
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Backend switching
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
await scene.switchBackend(newCanvas, 'webgl')
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Cleanup
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
scene.destroy()
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Coordinate system
|
|
295
|
+
|
|
296
|
+
Right-handed Z-up (Blender convention): +X right, +Y forward, +Z up.
|
|
297
|
+
|
|
298
|
+
## Development
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
bun install
|
|
302
|
+
bun run dev
|
|
303
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
interface WasmExports {
|
|
2
|
+
memory: WebAssembly.Memory;
|
|
3
|
+
vc_init: (pages: number) => number;
|
|
4
|
+
vc_perspective: (offset: number, fov: number, aspect: number, near: number, far: number) => void;
|
|
5
|
+
vc_look_at: (offset: number, ex: number, ey: number, ez: number, tx: number, ty: number, tz: number, ux: number, uy: number, uz: number) => void;
|
|
6
|
+
vc_m4_multiply: (out: number, a: number, b: number) => void;
|
|
7
|
+
vc_frame_reset: () => void;
|
|
8
|
+
vc_compute_world_matrices: (count: number) => number;
|
|
9
|
+
vc_get_positions_ptr: () => number;
|
|
10
|
+
vc_get_euler_rotations_ptr: () => number;
|
|
11
|
+
vc_get_scales_ptr: () => number;
|
|
12
|
+
vc_get_world_matrices_ptr: () => number;
|
|
13
|
+
vc_get_colors_ptr: () => number;
|
|
14
|
+
vc_get_flags_ptr: () => number;
|
|
15
|
+
vc_get_bspheres_ptr: () => number;
|
|
16
|
+
vc_get_geometry_ids_ptr: () => number;
|
|
17
|
+
vc_get_sort_keys_ptr: () => number;
|
|
18
|
+
vc_get_visible_indices_ptr: () => number;
|
|
19
|
+
vc_extract_frustum_planes: (vpOffset: number, planesOffset: number) => void;
|
|
20
|
+
vc_frustum_cull: (count: number, planesOffset: number, bspheresOffset: number, flagsOffset: number, outOffset: number) => number;
|
|
21
|
+
vc_build_sort_keys: (count: number, visibleOffset: number, geoIdsOffset: number, keysOutOffset: number) => void;
|
|
22
|
+
vc_sort_draw_calls: (count: number, keysOffset: number, indicesOffset: number) => void;
|
|
23
|
+
}
|
|
24
|
+
interface WasmCore {
|
|
25
|
+
exports: WasmExports;
|
|
26
|
+
memory: WebAssembly.Memory;
|
|
27
|
+
f32: Float32Array;
|
|
28
|
+
u32: Uint32Array;
|
|
29
|
+
u8: Uint8Array;
|
|
30
|
+
scratchOffset: number;
|
|
31
|
+
positionsPtr: number;
|
|
32
|
+
eulerRotationsPtr: number;
|
|
33
|
+
scalesPtr: number;
|
|
34
|
+
worldMatricesPtr: number;
|
|
35
|
+
colorsPtr: number;
|
|
36
|
+
flagsPtr: number;
|
|
37
|
+
bspheresPtr: number;
|
|
38
|
+
geometryIdsPtr: number;
|
|
39
|
+
sortKeysPtr: number;
|
|
40
|
+
visibleIndicesPtr: number;
|
|
41
|
+
}
|
|
42
|
+
declare function loadWasm(): Promise<WasmCore>;
|
|
43
|
+
|
|
44
|
+
interface DrawEntity {
|
|
45
|
+
worldMatrix: Float32Array;
|
|
46
|
+
color: Float32Array;
|
|
47
|
+
geometryId: number;
|
|
48
|
+
unlit: boolean;
|
|
49
|
+
}
|
|
50
|
+
interface Renderer {
|
|
51
|
+
backend: Backend;
|
|
52
|
+
registerGeometry(id: number, vertices: Float32Array, indices: Uint16Array | Uint32Array): void;
|
|
53
|
+
updateCamera(view: Float32Array, projection: Float32Array): void;
|
|
54
|
+
updateLighting(dir: Float32Array, dirColor: Float32Array, ambient: Float32Array): void;
|
|
55
|
+
draw(entities: DrawEntity[], count: number): void;
|
|
56
|
+
resize(width: number, height: number): void;
|
|
57
|
+
destroy(): void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type Backend = 'webgpu' | 'webgl';
|
|
61
|
+
declare function createRenderer(canvas: HTMLCanvasElement, backend?: Backend): Promise<Renderer>;
|
|
62
|
+
|
|
63
|
+
declare class Camera {
|
|
64
|
+
eye: Float32Array<ArrayBuffer>;
|
|
65
|
+
target: Float32Array<ArrayBuffer>;
|
|
66
|
+
up: Float32Array<ArrayBuffer>;
|
|
67
|
+
fov: number;
|
|
68
|
+
near: number;
|
|
69
|
+
far: number;
|
|
70
|
+
view: Float32Array<ArrayBuffer>;
|
|
71
|
+
projection: Float32Array<ArrayBuffer>;
|
|
72
|
+
vp: Float32Array<ArrayBuffer>;
|
|
73
|
+
private viewOffset;
|
|
74
|
+
private projOffset;
|
|
75
|
+
private vpOffset;
|
|
76
|
+
constructor(scratchOffset: number);
|
|
77
|
+
update(wasm: WasmCore, aspect: number): void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface MeshOptions {
|
|
81
|
+
geometryId: number;
|
|
82
|
+
position?: [number, number, number];
|
|
83
|
+
rotation?: [number, number, number];
|
|
84
|
+
scale?: [number, number, number];
|
|
85
|
+
color?: [number, number, number, number];
|
|
86
|
+
visible?: boolean;
|
|
87
|
+
unlit?: boolean;
|
|
88
|
+
}
|
|
89
|
+
declare class Mesh {
|
|
90
|
+
entityId: number;
|
|
91
|
+
position: Float32Array;
|
|
92
|
+
rotation: Float32Array;
|
|
93
|
+
scale: Float32Array;
|
|
94
|
+
color: Float32Array;
|
|
95
|
+
worldMatrix: Float32Array;
|
|
96
|
+
private wasm;
|
|
97
|
+
private flags;
|
|
98
|
+
geometryId: number;
|
|
99
|
+
private initOptions;
|
|
100
|
+
bsphereRadius: number;
|
|
101
|
+
bsphereCenterOffset: Float32Array<ArrayBuffer>;
|
|
102
|
+
constructor(options: MeshOptions);
|
|
103
|
+
/** Called by Scene when entity is assigned */
|
|
104
|
+
_bind(wasm: WasmCore, entityId: number): void;
|
|
105
|
+
setDirty(): void;
|
|
106
|
+
get visible(): boolean;
|
|
107
|
+
set visible(v: boolean);
|
|
108
|
+
get unlit(): boolean;
|
|
109
|
+
set unlit(v: boolean);
|
|
110
|
+
/** Update bounding sphere in WASM memory */
|
|
111
|
+
updateBsphere(): void;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface Geometry {
|
|
115
|
+
vertices: Float32Array;
|
|
116
|
+
indices: Uint16Array;
|
|
117
|
+
}
|
|
118
|
+
declare function createBoxGeometry(w?: number, h?: number, d?: number): Geometry;
|
|
119
|
+
declare function createSphereGeometry(radius?: number, wSegs?: number, hSegs?: number): Geometry;
|
|
120
|
+
|
|
121
|
+
interface SceneConfig {
|
|
122
|
+
backend?: Backend;
|
|
123
|
+
}
|
|
124
|
+
declare class Scene {
|
|
125
|
+
wasm: WasmCore;
|
|
126
|
+
renderer: Renderer;
|
|
127
|
+
camera: Camera;
|
|
128
|
+
canvas: HTMLCanvasElement;
|
|
129
|
+
private meshes;
|
|
130
|
+
private activeCount;
|
|
131
|
+
private geometryRegistry;
|
|
132
|
+
private nextGeometryId;
|
|
133
|
+
private config;
|
|
134
|
+
private lightDir;
|
|
135
|
+
private lightColor;
|
|
136
|
+
private ambientColor;
|
|
137
|
+
visibleCount: number;
|
|
138
|
+
drawCalls: number;
|
|
139
|
+
private planesOffset;
|
|
140
|
+
private constructor();
|
|
141
|
+
static create(canvas: HTMLCanvasElement, config?: SceneConfig): Promise<Scene>;
|
|
142
|
+
registerGeometry(geometry: Geometry): number;
|
|
143
|
+
add(mesh: Mesh): Mesh;
|
|
144
|
+
remove(mesh: Mesh): void;
|
|
145
|
+
setDirectionalLight(direction: [number, number, number], color: [number, number, number]): void;
|
|
146
|
+
setAmbientLight(color: [number, number, number]): void;
|
|
147
|
+
render(): void;
|
|
148
|
+
resize(width: number, height: number): void;
|
|
149
|
+
switchBackend(canvas: HTMLCanvasElement, backend: Backend): Promise<void>;
|
|
150
|
+
destroy(): void;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
declare const shaderSource = "\nstruct CameraUniforms {\n view: mat4x4f,\n projection: mat4x4f,\n}\n\nstruct ModelUniforms {\n world: mat4x4f,\n color: vec4f,\n flags: vec4f, // x = unlit\n}\n\nstruct LightUniforms {\n direction: vec4f,\n color: vec4f,\n ambient: vec4f,\n}\n\n@group(0) @binding(0) var<uniform> camera: CameraUniforms;\n@group(1) @binding(0) var<uniform> model: ModelUniforms;\n@group(2) @binding(0) var<uniform> light: LightUniforms;\n\nstruct VertexInput {\n @location(0) position: vec3f,\n @location(1) normal: vec3f,\n}\n\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) worldNormal: vec3f,\n @location(1) color: vec4f,\n @location(2) unlit: f32,\n}\n\n@vertex\nfn vs_main(input: VertexInput) -> VertexOutput {\n var output: VertexOutput;\n\n let worldPos = model.world * vec4f(input.position, 1.0);\n output.position = camera.projection * camera.view * worldPos;\n\n // Transform normal by upper 3x3 of world matrix\n let normalMat = mat3x3f(\n model.world[0].xyz,\n model.world[1].xyz,\n model.world[2].xyz,\n );\n output.worldNormal = normalize(normalMat * input.normal);\n output.color = model.color;\n output.unlit = model.flags.x;\n\n return output;\n}\n\n@fragment\nfn fs_main(input: VertexOutput) -> @location(0) vec4f {\n let normal = normalize(input.worldNormal);\n let lightDir = normalize(-light.direction.xyz);\n\n // Lambert diffuse\n let NdotL = max(dot(normal, lightDir), 0.0);\n let diffuse = light.color.rgb * NdotL;\n let ambient = light.ambient.rgb;\n\n var finalColor: vec3f;\n if (input.unlit > 0.5) {\n finalColor = input.color.rgb;\n } else {\n finalColor = input.color.rgb * (diffuse + ambient);\n }\n\n return vec4f(finalColor, input.color.a);\n}\n";
|
|
154
|
+
|
|
155
|
+
declare const vertexShaderGLSL = "#version 300 es\nprecision highp float;\n\nlayout(std140) uniform CameraUniforms {\n mat4 view;\n mat4 projection;\n} camera;\n\nlayout(std140) uniform ModelUniforms {\n mat4 world;\n vec4 color;\n vec4 flags; // x = unlit\n} model;\n\nlayout(location = 0) in vec3 a_position;\nlayout(location = 1) in vec3 a_normal;\n\nout vec3 v_worldNormal;\nout vec4 v_color;\nout float v_unlit;\n\nvoid main() {\n vec4 worldPos = model.world * vec4(a_position, 1.0);\n gl_Position = camera.projection * camera.view * worldPos;\n\n mat3 normalMat = mat3(model.world);\n v_worldNormal = normalize(normalMat * a_normal);\n v_color = model.color;\n v_unlit = model.flags.x;\n}\n";
|
|
156
|
+
declare const fragmentShaderGLSL = "#version 300 es\nprecision highp float;\n\nlayout(std140) uniform LightUniforms {\n vec4 direction;\n vec4 color;\n vec4 ambient;\n} light;\n\nin vec3 v_worldNormal;\nin vec4 v_color;\nin float v_unlit;\n\nout vec4 fragColor;\n\nvoid main() {\n vec3 normal = normalize(v_worldNormal);\n vec3 lightDir = normalize(-light.direction.xyz);\n\n float NdotL = max(dot(normal, lightDir), 0.0);\n vec3 diffuse = light.color.rgb * NdotL;\n vec3 ambient = light.ambient.rgb;\n\n vec3 finalColor;\n if (v_unlit > 0.5) {\n finalColor = v_color.rgb;\n } else {\n finalColor = v_color.rgb * (diffuse + ambient);\n }\n\n fragColor = vec4(finalColor, v_color.a);\n}\n";
|
|
157
|
+
|
|
158
|
+
export { type Backend, Camera, type Geometry, Mesh, type MeshOptions, type Renderer, Scene, type SceneConfig, type WasmCore, type WasmExports, createBoxGeometry, createRenderer, createSphereGeometry, fragmentShaderGLSL, loadWasm, shaderSource, vertexShaderGLSL };
|