@tty-pt/lamina 0.0.0-stage → 1.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/LICENSE +24 -0
- package/README.md +55 -2
- package/dist/bridge.d.ts +10 -0
- package/dist/bridge.js +430 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +263 -0
- package/dist/lamina.wasm +0 -0
- package/dist/renderer.js +143 -0
- package/include/ttypt/lam-cam.h +150 -0
- package/include/ttypt/lam-font.h +119 -0
- package/include/ttypt/lam-key.h +211 -0
- package/include/ttypt/lam-tm.h +148 -0
- package/include/ttypt/lam-ui.h +426 -0
- package/include/ttypt/lam.h +372 -0
- package/include/ttypt/webgl.h +286 -0
- package/package.json +49 -4
- package/prebuilds/darwin-arm64/lamina.node +0 -0
- package/prebuilds/darwin-x64/lamina.node +0 -0
- package/prebuilds/linux-x64/lamina.node +0 -0
- package/prebuilds/win32-x64/lamina.node +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, tty-pt
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tty-pt/lamina
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](#)
|
|
4
|
+
[](#)
|
|
5
|
+
[](#)
|
|
6
|
+
[](#)
|
|
7
|
+
|
|
8
|
+
> Universal Node.js and Browser bindings for **`liblamina`**, the 2D immediate-mode canvas graphics, tilemap rendering, and WebGL viewport engine.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Quickstart
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @tty-pt/lamina
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { ViewportController } from "@tty-pt/lamina";
|
|
20
|
+
|
|
21
|
+
const viewport = new ViewportController(1280, 720);
|
|
22
|
+
viewport.setCamera(100, 200, 1.5 /* zoom */);
|
|
23
|
+
|
|
24
|
+
// Transform game/map coordinates to canvas screen pixels
|
|
25
|
+
const screen = viewport.worldToScreen(100, 200);
|
|
26
|
+
console.log(screen); // { x: 640, y: 360 }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 2. JavaScript / TypeScript API Reference
|
|
32
|
+
|
|
33
|
+
### `ViewportController`
|
|
34
|
+
2D tilemap and canvas camera viewport controller:
|
|
35
|
+
* `constructor(width?: number, height?: number)`: Initializes viewport with dimensions (default: 640x480).
|
|
36
|
+
* `.setCamera(x: number, y: number, zoom?: number): void`: Updates camera center position and zoom factor.
|
|
37
|
+
* `.worldToScreen(worldX: number, worldY: number): { x: number, y: number }`: Projects continuous world coordinates to screen canvas pixels. **Executes strictly in compiled WebAssembly / C** via `lamina_wasm_world_to_screen()`.
|
|
38
|
+
* `.screenToWorld(screenX: number, screenY: number): { x: number, y: number }`: Unprojects screen canvas pixel coordinates back to continuous world coordinates strictly via WebAssembly.
|
|
39
|
+
|
|
40
|
+
### `getLaminaWasm(): WebAssembly.Instance`
|
|
41
|
+
Returns the WebAssembly instance loaded on the Stele shared-memory substrate.
|
|
42
|
+
|
|
43
|
+
### `version(): string`
|
|
44
|
+
Returns the library semantic version string.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 3. C Development Headers via NPM
|
|
49
|
+
|
|
50
|
+
```makefile
|
|
51
|
+
CFLAGS += -I./node_modules/@tty-pt/lamina/include
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
BSD 2-Clause License. Copyright (c) 2026, tty-pt.
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function createLaminaBridge(root?: any, getWasm?: () => any): {
|
|
2
|
+
mount(): void;
|
|
3
|
+
makeLaminaImports(): {
|
|
4
|
+
lam_canvas_flush(buf_ptr: number, width: number, height: number): void;
|
|
5
|
+
lam_fill(): void;
|
|
6
|
+
lam_tile_draw(): void;
|
|
7
|
+
lam_flush(): void;
|
|
8
|
+
};
|
|
9
|
+
destroy(): void;
|
|
10
|
+
};
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* lamina-bridge.js — Browser WebAssembly bridge for liblamina 2D canvas flush.
|
|
3
|
+
* Provides the "lamina" WASM import module.
|
|
4
|
+
*
|
|
5
|
+
* Automatically mounts onto active [data-bud-canvas] elements.
|
|
6
|
+
* All 3D scene, shader, camera, mesh, and billboard logic lives in pure C over <ttypt/webgl.h>.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
function getExport(exports, names) {
|
|
10
|
+
if (!exports) return null;
|
|
11
|
+
for (const name of names) {
|
|
12
|
+
if (typeof exports[name] === 'function') return exports[name];
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const VS_QUAD = `attribute vec2 a_pos;
|
|
18
|
+
attribute vec2 a_uv;
|
|
19
|
+
varying vec2 v_uv;
|
|
20
|
+
void main() {
|
|
21
|
+
v_uv = a_uv;
|
|
22
|
+
gl_Position = vec4(a_pos, 0.0, 1.0);
|
|
23
|
+
}`;
|
|
24
|
+
|
|
25
|
+
const FS_QUAD = `precision mediump float;
|
|
26
|
+
varying vec2 v_uv;
|
|
27
|
+
uniform sampler2D u_tex;
|
|
28
|
+
void main() {
|
|
29
|
+
gl_FragColor = texture2D(u_tex, v_uv);
|
|
30
|
+
}`;
|
|
31
|
+
|
|
32
|
+
const shaderDefs = new Map();
|
|
33
|
+
let nextShaderId = 1;
|
|
34
|
+
let activeShaderId = 0;
|
|
35
|
+
|
|
36
|
+
const imageIdsByUrl = new Map();
|
|
37
|
+
const imageCache = new Map();
|
|
38
|
+
let nextImageId = 1;
|
|
39
|
+
|
|
40
|
+
function compileProgram(gl, vsSrc, fsSrc) {
|
|
41
|
+
try {
|
|
42
|
+
const vs = gl.createShader(gl.VERTEX_SHADER);
|
|
43
|
+
gl.shaderSource(vs, vsSrc);
|
|
44
|
+
gl.compileShader(vs);
|
|
45
|
+
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) return null;
|
|
46
|
+
|
|
47
|
+
const fs = gl.createShader(gl.FRAGMENT_SHADER);
|
|
48
|
+
gl.shaderSource(fs, fsSrc);
|
|
49
|
+
gl.compileShader(fs);
|
|
50
|
+
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) return null;
|
|
51
|
+
|
|
52
|
+
const prog = gl.createProgram();
|
|
53
|
+
gl.attachShader(prog, vs);
|
|
54
|
+
gl.attachShader(prog, fs);
|
|
55
|
+
gl.linkProgram(prog);
|
|
56
|
+
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) return null;
|
|
57
|
+
|
|
58
|
+
const aPos = gl.getAttribLocation(prog, 'a_pos');
|
|
59
|
+
const aUv = gl.getAttribLocation(prog, 'a_uv');
|
|
60
|
+
const uTex = gl.getUniformLocation(prog, 'u_tex');
|
|
61
|
+
const uTime = gl.getUniformLocation(prog, 'u_time');
|
|
62
|
+
const uRes = gl.getUniformLocation(prog, 'u_res');
|
|
63
|
+
const uParams = gl.getUniformLocation(prog, 'u_params');
|
|
64
|
+
return { prog, aPos, aUv, uTex, uTime, uRes, uParams };
|
|
65
|
+
} catch (e) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function initGlPipeline(gl) {
|
|
71
|
+
try {
|
|
72
|
+
const basePipe = compileProgram(gl, VS_QUAD, FS_QUAD);
|
|
73
|
+
if (!basePipe) return null;
|
|
74
|
+
gl.useProgram(basePipe.prog);
|
|
75
|
+
|
|
76
|
+
const verts = new Float32Array([
|
|
77
|
+
-1.0, -1.0, 0.0, 1.0,
|
|
78
|
+
1.0, -1.0, 1.0, 1.0,
|
|
79
|
+
-1.0, 1.0, 0.0, 0.0,
|
|
80
|
+
-1.0, 1.0, 0.0, 0.0,
|
|
81
|
+
1.0, -1.0, 1.0, 1.0,
|
|
82
|
+
1.0, 1.0, 1.0, 0.0
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
const vbo = gl.createBuffer();
|
|
86
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
87
|
+
gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW);
|
|
88
|
+
|
|
89
|
+
const tex = gl.createTexture();
|
|
90
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
91
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
92
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
93
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
94
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
95
|
+
|
|
96
|
+
return { prog: basePipe.prog, aPos: basePipe.aPos, aUv: basePipe.aUv, tex, vbo, uTex: basePipe.uTex, uTime: basePipe.uTime, uRes: basePipe.uRes };
|
|
97
|
+
} catch (e) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function createLaminaBridge(root, getWasm) {
|
|
103
|
+
const targets = new WeakMap();
|
|
104
|
+
const kicked = new WeakSet();
|
|
105
|
+
const globalScope = typeof window !== 'undefined' ? window : globalThis;
|
|
106
|
+
|
|
107
|
+
function allCanvases() {
|
|
108
|
+
if (root && typeof root.querySelectorAll === 'function') {
|
|
109
|
+
let list = root.querySelectorAll('[data-bud-canvas]');
|
|
110
|
+
if (!list.length && typeof document !== 'undefined' && root !== document && typeof document.querySelectorAll === 'function') {
|
|
111
|
+
list = document.querySelectorAll('[data-bud-canvas]');
|
|
112
|
+
}
|
|
113
|
+
return list;
|
|
114
|
+
}
|
|
115
|
+
if (typeof document !== 'undefined' && typeof document.querySelectorAll === 'function') {
|
|
116
|
+
return document.querySelectorAll('[data-bud-canvas]');
|
|
117
|
+
}
|
|
118
|
+
if (globalScope.__stele_worker_canvases instanceof Map) {
|
|
119
|
+
const res = [];
|
|
120
|
+
for (const entry of globalScope.__stele_worker_canvases.values()) {
|
|
121
|
+
if (entry && entry.canvas) res.push(entry.canvas);
|
|
122
|
+
}
|
|
123
|
+
return res;
|
|
124
|
+
}
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isFitCanvas(c) {
|
|
129
|
+
if (!c) return false;
|
|
130
|
+
if (c.__stele_fit) return true;
|
|
131
|
+
return !!(c.hasAttribute && c.hasAttribute('data-bud-canvas-fit'));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function pickCanvas(width, height) {
|
|
135
|
+
const list = allCanvases();
|
|
136
|
+
let first = null, fit = null, fixed = null;
|
|
137
|
+
for (const c of list) {
|
|
138
|
+
if (!first) first = c;
|
|
139
|
+
const isFit = isFitCanvas(c);
|
|
140
|
+
if (!isFit && c.width === width && c.height === height) return c;
|
|
141
|
+
if (!fixed && !isFit) fixed = c;
|
|
142
|
+
if (!fit && isFit) fit = c;
|
|
143
|
+
}
|
|
144
|
+
if (fixed && (!fit || (width <= 320 && height <= 320))) {
|
|
145
|
+
return fixed;
|
|
146
|
+
}
|
|
147
|
+
if (fit && width > 0 && height > 0) {
|
|
148
|
+
fit.width = width;
|
|
149
|
+
fit.height = height;
|
|
150
|
+
return fit;
|
|
151
|
+
}
|
|
152
|
+
return fixed || fit || first;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function targetFor(el) {
|
|
156
|
+
let t = targets.get(el);
|
|
157
|
+
if (t) return t;
|
|
158
|
+
t = { gl: null, glPipe: null, canvas2d: null, shaderPrograms: new Map() };
|
|
159
|
+
try {
|
|
160
|
+
const glOpts = { preserveDrawingBuffer: true, alpha: true, antialias: true, depth: true };
|
|
161
|
+
t.gl = el.getContext('webgl2', glOpts) || el.getContext('webgl', glOpts);
|
|
162
|
+
if (t.gl) {
|
|
163
|
+
t.glPipe = initGlPipeline(t.gl);
|
|
164
|
+
}
|
|
165
|
+
} catch (e) {
|
|
166
|
+
t.gl = null;
|
|
167
|
+
t.glPipe = null;
|
|
168
|
+
}
|
|
169
|
+
if (!t.gl || !t.glPipe) {
|
|
170
|
+
t.gl = null;
|
|
171
|
+
t.glPipe = null;
|
|
172
|
+
try {
|
|
173
|
+
t.canvas2d = el.getContext('2d');
|
|
174
|
+
} catch (e) {
|
|
175
|
+
t.canvas2d = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
targets.set(el, t);
|
|
179
|
+
return t;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function kickRender() {
|
|
183
|
+
const fn = getExport(getWasm ? getWasm() : null, ['wasm_canvas_render']);
|
|
184
|
+
if (fn && typeof requestAnimationFrame === 'function') requestAnimationFrame(() => fn());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
mount() {
|
|
189
|
+
for (const c of allCanvases()) {
|
|
190
|
+
if (!kicked.has(c)) {
|
|
191
|
+
kicked.add(c);
|
|
192
|
+
kickRender();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
makeLaminaImports() {
|
|
197
|
+
return {
|
|
198
|
+
lam_canvas_flush(buf_ptr, width, height) {
|
|
199
|
+
const canvasEl = pickCanvas(width, height);
|
|
200
|
+
if (!canvasEl) return;
|
|
201
|
+
const t = targetFor(canvasEl);
|
|
202
|
+
if (!kicked.has(canvasEl)) {
|
|
203
|
+
kicked.add(canvasEl);
|
|
204
|
+
kickRender();
|
|
205
|
+
}
|
|
206
|
+
const exports = getWasm();
|
|
207
|
+
if (!exports || !exports.memory) return;
|
|
208
|
+
const uptr = buf_ptr >>> 0;
|
|
209
|
+
const byteLen = width * height * 4;
|
|
210
|
+
if (uptr + byteLen > exports.memory.buffer.byteLength) return;
|
|
211
|
+
|
|
212
|
+
const bytes = new Uint8Array(exports.memory.buffer, uptr, byteLen);
|
|
213
|
+
|
|
214
|
+
if (t.gl && t.glPipe) {
|
|
215
|
+
const gl = t.gl;
|
|
216
|
+
gl.viewport(0, 0, canvasEl.width || width, canvasEl.height || height);
|
|
217
|
+
gl.disable(gl.DEPTH_TEST);
|
|
218
|
+
gl.disable(gl.BLEND);
|
|
219
|
+
gl.depthMask(true);
|
|
220
|
+
gl.colorMask(true, true, true, true);
|
|
221
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
|
|
222
|
+
|
|
223
|
+
let pipe = t.glPipe;
|
|
224
|
+
let def = null;
|
|
225
|
+
if (activeShaderId > 0 && shaderDefs.has(activeShaderId)) {
|
|
226
|
+
def = shaderDefs.get(activeShaderId);
|
|
227
|
+
if (!t.shaderPrograms.has(activeShaderId)) {
|
|
228
|
+
const cp = compileProgram(gl, VS_QUAD, def.fragSrc);
|
|
229
|
+
t.shaderPrograms.set(activeShaderId, cp);
|
|
230
|
+
}
|
|
231
|
+
const cp = t.shaderPrograms.get(activeShaderId);
|
|
232
|
+
if (cp) pipe = cp;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
gl.useProgram(pipe.prog);
|
|
236
|
+
if (pipe.uTime) gl.uniform1f(pipe.uTime, performance.now() * 0.001);
|
|
237
|
+
if (pipe.uRes) gl.uniform2f(pipe.uRes, width, height);
|
|
238
|
+
if (pipe.uParams && def && def.uniforms) gl.uniform4fv(pipe.uParams, def.uniforms);
|
|
239
|
+
|
|
240
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, t.glPipe.vbo);
|
|
241
|
+
if (pipe.aPos >= 0) {
|
|
242
|
+
gl.enableVertexAttribArray(pipe.aPos);
|
|
243
|
+
gl.vertexAttribPointer(pipe.aPos, 2, gl.FLOAT, false, 16, 0);
|
|
244
|
+
}
|
|
245
|
+
if (pipe.aUv >= 0) {
|
|
246
|
+
gl.enableVertexAttribArray(pipe.aUv);
|
|
247
|
+
gl.vertexAttribPointer(pipe.aUv, 2, gl.FLOAT, false, 16, 8);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
gl.bindTexture(gl.TEXTURE_2D, t.glPipe.tex);
|
|
251
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, bytes);
|
|
252
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
253
|
+
canvasEl.__stele_dirty = true;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (t.canvas2d) {
|
|
258
|
+
const clamped = new Uint8ClampedArray(exports.memory.buffer, uptr, byteLen);
|
|
259
|
+
const imgData = new ImageData(clamped, width, height);
|
|
260
|
+
t.canvas2d.putImageData(imgData, 0, 0);
|
|
261
|
+
canvasEl.__stele_dirty = true;
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
lam_shader_register(name_ptr, frag_ptr) {
|
|
265
|
+
const exports = getWasm();
|
|
266
|
+
if (!exports || !exports.memory) return 0;
|
|
267
|
+
const mem = new Uint8Array(exports.memory.buffer);
|
|
268
|
+
let p = name_ptr >>> 0, name = "";
|
|
269
|
+
while (p < mem.length && mem[p] !== 0) name += String.fromCharCode(mem[p++]);
|
|
270
|
+
p = frag_ptr >>> 0;
|
|
271
|
+
let fragSrc = "";
|
|
272
|
+
while (p < mem.length && mem[p] !== 0) fragSrc += String.fromCharCode(mem[p++]);
|
|
273
|
+
|
|
274
|
+
const id = nextShaderId++;
|
|
275
|
+
shaderDefs.set(id, { name, fragSrc, uniforms: new Float32Array(16) });
|
|
276
|
+
return id;
|
|
277
|
+
},
|
|
278
|
+
lam_shader_bind(shader_id) {
|
|
279
|
+
activeShaderId = shader_id >>> 0;
|
|
280
|
+
},
|
|
281
|
+
lam_shader_uniforms(shader_id, vals_ptr, count) {
|
|
282
|
+
const exports = getWasm();
|
|
283
|
+
if (!exports || !exports.memory) return;
|
|
284
|
+
const id = shader_id >>> 0;
|
|
285
|
+
const def = shaderDefs.get(id);
|
|
286
|
+
if (!def) return;
|
|
287
|
+
const n = Math.min(count >>> 0, 16);
|
|
288
|
+
const f32 = new Float32Array(exports.memory.buffer, vals_ptr >>> 0, n);
|
|
289
|
+
def.uniforms.set(f32);
|
|
290
|
+
},
|
|
291
|
+
lam_cam_set_3d() {},
|
|
292
|
+
lam_cam_set_2d() {},
|
|
293
|
+
lam_terrain_mesh_flush() {},
|
|
294
|
+
lam_billboard_batch_flush() {},
|
|
295
|
+
lam_fill() {},
|
|
296
|
+
lam_tile_draw() {},
|
|
297
|
+
lam_flush() {},
|
|
298
|
+
lam_image_request(url_ptr) {
|
|
299
|
+
const exports = getWasm();
|
|
300
|
+
if (!exports || !exports.memory) return 0;
|
|
301
|
+
const mem = new Uint8Array(exports.memory.buffer);
|
|
302
|
+
let p = url_ptr >>> 0, url = "";
|
|
303
|
+
while (p < mem.length && mem[p] !== 0) url += String.fromCharCode(mem[p++]);
|
|
304
|
+
if (!url) return 0;
|
|
305
|
+
if (imageIdsByUrl.has(url)) return imageIdsByUrl.get(url);
|
|
306
|
+
|
|
307
|
+
const id = nextImageId++;
|
|
308
|
+
imageIdsByUrl.set(url, id);
|
|
309
|
+
const rec = { status: 0, w: 0, h: 0, data: null };
|
|
310
|
+
imageCache.set(id, rec);
|
|
311
|
+
|
|
312
|
+
const fetchUrl = (url.startsWith('/') && globalScope.__stele_origin)
|
|
313
|
+
? `${globalScope.__stele_origin}${url}`
|
|
314
|
+
: url;
|
|
315
|
+
|
|
316
|
+
if (typeof Image !== 'undefined') {
|
|
317
|
+
const img = new Image();
|
|
318
|
+
img.decoding = 'async';
|
|
319
|
+
img.onload = () => {
|
|
320
|
+
try {
|
|
321
|
+
const w = img.naturalWidth || img.width || 0;
|
|
322
|
+
const h = img.naturalHeight || img.height || 0;
|
|
323
|
+
if (w > 0 && h > 0) {
|
|
324
|
+
let off = null;
|
|
325
|
+
if (typeof OffscreenCanvas !== 'undefined') {
|
|
326
|
+
off = new OffscreenCanvas(w, h);
|
|
327
|
+
} else if (typeof document !== 'undefined' && document.createElement) {
|
|
328
|
+
off = document.createElement('canvas');
|
|
329
|
+
off.width = w;
|
|
330
|
+
off.height = h;
|
|
331
|
+
}
|
|
332
|
+
if (off) {
|
|
333
|
+
const ctx = off.getContext('2d');
|
|
334
|
+
if (ctx) {
|
|
335
|
+
ctx.clearRect(0, 0, w, h);
|
|
336
|
+
ctx.drawImage(img, 0, 0);
|
|
337
|
+
const idata = ctx.getImageData(0, 0, w, h);
|
|
338
|
+
rec.w = w;
|
|
339
|
+
rec.h = h;
|
|
340
|
+
rec.data = new Uint8Array(idata.data.buffer.slice(0));
|
|
341
|
+
rec.status = 1;
|
|
342
|
+
kickRender();
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
rec.status = 2;
|
|
348
|
+
} catch (e) {
|
|
349
|
+
rec.status = 2;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
img.onerror = () => { rec.status = 2; };
|
|
353
|
+
img.src = fetchUrl;
|
|
354
|
+
} else if (typeof fetch === 'function' && typeof createImageBitmap === 'function' && typeof OffscreenCanvas !== 'undefined') {
|
|
355
|
+
fetch(fetchUrl)
|
|
356
|
+
.then((res) => {
|
|
357
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
358
|
+
return res.blob();
|
|
359
|
+
})
|
|
360
|
+
.then((blob) => createImageBitmap(blob))
|
|
361
|
+
.then((bmp) => {
|
|
362
|
+
try {
|
|
363
|
+
const w = bmp.width || 0;
|
|
364
|
+
const h = bmp.height || 0;
|
|
365
|
+
if (w > 0 && h > 0) {
|
|
366
|
+
const off = new OffscreenCanvas(w, h);
|
|
367
|
+
const ctx = off.getContext('2d');
|
|
368
|
+
if (ctx) {
|
|
369
|
+
ctx.clearRect(0, 0, w, h);
|
|
370
|
+
ctx.drawImage(bmp, 0, 0);
|
|
371
|
+
const idata = ctx.getImageData(0, 0, w, h);
|
|
372
|
+
rec.w = w;
|
|
373
|
+
rec.h = h;
|
|
374
|
+
rec.data = new Uint8Array(idata.data.buffer.slice(0));
|
|
375
|
+
rec.status = 1;
|
|
376
|
+
if (typeof bmp.close === 'function') bmp.close();
|
|
377
|
+
kickRender();
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (typeof bmp.close === 'function') bmp.close();
|
|
382
|
+
rec.status = 2;
|
|
383
|
+
} catch (_) {
|
|
384
|
+
if (bmp && typeof bmp.close === 'function') bmp.close();
|
|
385
|
+
rec.status = 2;
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
.catch(() => {
|
|
389
|
+
rec.status = 2;
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return id;
|
|
393
|
+
},
|
|
394
|
+
lam_image_read(img_id, dst_ptr, max_bytes, out_w_ptr, out_h_ptr) {
|
|
395
|
+
const exports = getWasm();
|
|
396
|
+
if (!exports || !exports.memory) return 0;
|
|
397
|
+
const rec = imageCache.get(img_id >>> 0);
|
|
398
|
+
if (!rec || rec.status !== 1 || !rec.data) return 0;
|
|
399
|
+
const buf = exports.memory.buffer;
|
|
400
|
+
const dv = new DataView(buf);
|
|
401
|
+
if (out_w_ptr) dv.setUint32(out_w_ptr >>> 0, rec.w >>> 0, true);
|
|
402
|
+
if (out_h_ptr) dv.setUint32(out_h_ptr >>> 0, rec.h >>> 0, true);
|
|
403
|
+
const need = rec.data.byteLength;
|
|
404
|
+
const uptr = dst_ptr >>> 0;
|
|
405
|
+
if (uptr && (max_bytes >>> 0) >= need && uptr + need <= buf.byteLength) {
|
|
406
|
+
new Uint8Array(buf, uptr, need).set(rec.data);
|
|
407
|
+
return 1;
|
|
408
|
+
}
|
|
409
|
+
return 0;
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
},
|
|
413
|
+
destroy() {}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const _lamGlobal = typeof window !== 'undefined' ? window : globalThis;
|
|
418
|
+
_lamGlobal.__wasm_capability_registry = _lamGlobal.__wasm_capability_registry || {};
|
|
419
|
+
const _lamFactory = (root, getWasm) => {
|
|
420
|
+
const b = createLaminaBridge(root, getWasm);
|
|
421
|
+
return {
|
|
422
|
+
mount() { b.mount(); },
|
|
423
|
+
destroy() { b.destroy(); },
|
|
424
|
+
makeImports() { return b.makeLaminaImports(); }
|
|
425
|
+
};
|
|
426
|
+
};
|
|
427
|
+
_lamGlobal.__wasm_capability_registry.lamina = _lamFactory;
|
|
428
|
+
if (typeof _lamGlobal.registerWasmCapability === 'function') {
|
|
429
|
+
_lamGlobal.registerWasmCapability('lamina', _lamFactory);
|
|
430
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export function getLaminaWasm(): any;
|
|
2
|
+
export function createLaminaRenderer(canvasEl: any): {
|
|
3
|
+
isWebGL(): boolean;
|
|
4
|
+
render(rgbaBytes: ArrayBufferView, width: number, height: number): void;
|
|
5
|
+
destroy(): void;
|
|
6
|
+
};
|
|
7
|
+
export function createLaminaBridge(root?: any, getWasm?: () => any): any;
|
|
8
|
+
|
|
9
|
+
export function rgba(r: number, g: number, b: number, a?: number): number;
|
|
10
|
+
export function rgb(r: number, g: number, b: number): number;
|
|
11
|
+
export function colorR(c: number): number;
|
|
12
|
+
export function colorG(c: number): number;
|
|
13
|
+
export function colorB(c: number): number;
|
|
14
|
+
export function colorA(c: number): number;
|
|
15
|
+
|
|
16
|
+
export function attachCanvas(canvasEl: any): {
|
|
17
|
+
isWebGL(): boolean;
|
|
18
|
+
present(surface: LaminaSurface): void;
|
|
19
|
+
destroy(): void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class LaminaSurface {
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
wasmHandle: number;
|
|
26
|
+
canvas?: any;
|
|
27
|
+
constructor(width: number, height: number, options?: { canvas?: any });
|
|
28
|
+
free(): void;
|
|
29
|
+
clear(color?: number): this;
|
|
30
|
+
fill(x: number, y: number, w: number, h: number, color: number): this;
|
|
31
|
+
rect(x: number, y: number, w: number, h: number, color: number, thickness?: number): this;
|
|
32
|
+
line(x0: number, y0: number, x1: number, y1: number, color: number, thickness?: number): this;
|
|
33
|
+
paint(x: number, y: number, color: number): this;
|
|
34
|
+
pick(x: number, y: number): number;
|
|
35
|
+
getPixels(): Uint32Array;
|
|
36
|
+
getClampedPixels(): Uint8ClampedArray;
|
|
37
|
+
flush(targetCanvas?: any): this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class ViewportController {
|
|
41
|
+
width: number;
|
|
42
|
+
height: number;
|
|
43
|
+
cameraX: number;
|
|
44
|
+
cameraY: number;
|
|
45
|
+
zoom: number;
|
|
46
|
+
constructor(width?: number, height?: number);
|
|
47
|
+
setCamera(x: number, y: number, zoom?: number): void;
|
|
48
|
+
worldToScreen(worldX: number, worldY: number): { x: number; y: number };
|
|
49
|
+
screenToWorld(screenX: number, screenY: number): { x: number; y: number };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class FlexNode {
|
|
53
|
+
wasmHandle: number;
|
|
54
|
+
constructor(direction?: "row" | "column");
|
|
55
|
+
setSize(w: number, h: number): this;
|
|
56
|
+
setPadding(l: number, t: number, r: number, b: number): this;
|
|
57
|
+
setFlex(grow?: number, shrink?: number): this;
|
|
58
|
+
addChild(child: FlexNode): this;
|
|
59
|
+
computeLayout(availableWidth: number, availableHeight: number, startX?: number, startY?: number): this;
|
|
60
|
+
getBounds(): { x: number; y: number; width: number; height: number };
|
|
61
|
+
free(): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function version(): string;
|