@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/dist/index.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { loadWasmModule, loadNativeAddon, getSubstrate } from "@tty-pt/stele";
|
|
2
|
+
import { createLaminaRenderer } from "./renderer.js";
|
|
3
|
+
import { createLaminaBridge } from "./bridge.js";
|
|
4
|
+
|
|
5
|
+
export { createLaminaRenderer } from "./renderer.js";
|
|
6
|
+
export { createLaminaBridge } from "./bridge.js";
|
|
7
|
+
|
|
8
|
+
const nativeBinding = loadNativeAddon(import.meta.url, "lamina");
|
|
9
|
+
let wasmInstance = null;
|
|
10
|
+
|
|
11
|
+
export function getLaminaWasm() {
|
|
12
|
+
if (wasmInstance) return wasmInstance;
|
|
13
|
+
wasmInstance = loadWasmModule(new URL("./lamina.wasm", import.meta.url));
|
|
14
|
+
return wasmInstance;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Color packing helpers
|
|
19
|
+
*/
|
|
20
|
+
export function rgba(r, g, b, a = 255) {
|
|
21
|
+
return (((a & 0xff) << 24) | ((b & 0xff) << 16) | ((g & 0xff) << 8) | (r & 0xff)) >>> 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function rgb(r, g, b) {
|
|
25
|
+
return rgba(r, g, b, 255);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function colorR(c) {
|
|
29
|
+
return c & 0xff;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function colorG(c) {
|
|
33
|
+
return (c >> 8) & 0xff;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function colorB(c) {
|
|
37
|
+
return (c >> 16) & 0xff;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function colorA(c) {
|
|
41
|
+
return (c >>> 24) & 0xff;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* attachCanvas: Creates an automatic hardware-accelerated WebGL / 2D renderer target for a canvas.
|
|
46
|
+
*/
|
|
47
|
+
export function attachCanvas(canvasEl) {
|
|
48
|
+
const renderer = createLaminaRenderer(canvasEl);
|
|
49
|
+
return {
|
|
50
|
+
isWebGL: () => renderer.isWebGL(),
|
|
51
|
+
present: (surface) => {
|
|
52
|
+
renderer.render(surface.getClampedPixels(), surface.width, surface.height);
|
|
53
|
+
},
|
|
54
|
+
destroy: () => renderer.destroy()
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* LaminaSurface: Pure-C immediate-mode 2D software framebuffer
|
|
60
|
+
*/
|
|
61
|
+
export class LaminaSurface {
|
|
62
|
+
constructor(width, height, options = {}) {
|
|
63
|
+
this.width = width;
|
|
64
|
+
this.height = height;
|
|
65
|
+
this.canvas = options.canvas || null;
|
|
66
|
+
this._renderer = null;
|
|
67
|
+
|
|
68
|
+
const wasm = getLaminaWasm();
|
|
69
|
+
this.wasmHandle = wasm.exports.lamina_surface_new(width, height);
|
|
70
|
+
if (!this.wasmHandle) {
|
|
71
|
+
throw new Error(`Failed to allocate LaminaSurface(${width}x${height})`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.canvas) {
|
|
75
|
+
this._renderer = createLaminaRenderer(this.canvas);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
free() {
|
|
80
|
+
if (this._renderer) {
|
|
81
|
+
this._renderer.destroy();
|
|
82
|
+
this._renderer = null;
|
|
83
|
+
}
|
|
84
|
+
if (this.wasmHandle) {
|
|
85
|
+
const wasm = getLaminaWasm();
|
|
86
|
+
wasm.exports.lamina_surface_free(this.wasmHandle);
|
|
87
|
+
this.wasmHandle = 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
clear(color = 0x00000000) {
|
|
92
|
+
const wasm = getLaminaWasm();
|
|
93
|
+
wasm.exports.lamina_surface_clear(this.wasmHandle, color >>> 0);
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fill(x, y, w, h, color) {
|
|
98
|
+
const wasm = getLaminaWasm();
|
|
99
|
+
wasm.exports.lamina_surface_fill(this.wasmHandle, x, y, w, h, color >>> 0);
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
rect(x, y, w, h, color, thickness = 1) {
|
|
104
|
+
const wasm = getLaminaWasm();
|
|
105
|
+
wasm.exports.lamina_surface_rect(this.wasmHandle, x, y, w, h, color >>> 0, thickness);
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
line(x0, y0, x1, y1, color, thickness = 1) {
|
|
110
|
+
const wasm = getLaminaWasm();
|
|
111
|
+
wasm.exports.lamina_surface_line(this.wasmHandle, x0, y0, x1, y1, color >>> 0, thickness);
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
paint(x, y, color) {
|
|
116
|
+
const wasm = getLaminaWasm();
|
|
117
|
+
wasm.exports.lamina_surface_paint(this.wasmHandle, x, y, color >>> 0);
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pick(x, y) {
|
|
122
|
+
const wasm = getLaminaWasm();
|
|
123
|
+
return wasm.exports.lamina_surface_pick(this.wasmHandle, x, y) >>> 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getPixels() {
|
|
127
|
+
const wasm = getLaminaWasm();
|
|
128
|
+
const ptr = wasm.exports.lamina_surface_get_pixels(this.wasmHandle);
|
|
129
|
+
const mem = (wasm.exports.memory || getSubstrate().memory).buffer;
|
|
130
|
+
return new Uint32Array(mem, ptr, this.width * this.height);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getClampedPixels() {
|
|
134
|
+
const wasm = getLaminaWasm();
|
|
135
|
+
const ptr = wasm.exports.lamina_surface_get_pixels(this.wasmHandle);
|
|
136
|
+
const mem = (wasm.exports.memory || getSubstrate().memory).buffer;
|
|
137
|
+
return new Uint8ClampedArray(mem, ptr, this.width * this.height * 4);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
flush(targetCanvas = this.canvas) {
|
|
141
|
+
if (!targetCanvas) {
|
|
142
|
+
throw new Error("No canvas element specified for LaminaSurface.flush()");
|
|
143
|
+
}
|
|
144
|
+
if (!this._renderer || this.canvas !== targetCanvas) {
|
|
145
|
+
if (this._renderer) this._renderer.destroy();
|
|
146
|
+
this.canvas = targetCanvas;
|
|
147
|
+
this._renderer = createLaminaRenderer(targetCanvas);
|
|
148
|
+
}
|
|
149
|
+
this._renderer.render(this.getClampedPixels(), this.width, this.height);
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* ViewportController: Manages canvas / tilemap viewport transformations
|
|
156
|
+
*/
|
|
157
|
+
export class ViewportController {
|
|
158
|
+
constructor(width = 640, height = 480) {
|
|
159
|
+
this.width = width;
|
|
160
|
+
this.height = height;
|
|
161
|
+
this.cameraX = 0;
|
|
162
|
+
this.cameraY = 0;
|
|
163
|
+
this.zoom = 1.0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
setCamera(x, y, zoom = this.zoom) {
|
|
167
|
+
this.cameraX = x;
|
|
168
|
+
this.cameraY = y;
|
|
169
|
+
this.zoom = zoom;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
worldToScreen(worldX, worldY) {
|
|
173
|
+
if (nativeBinding && nativeBinding.worldToScreen) {
|
|
174
|
+
return nativeBinding.worldToScreen(worldX, worldY, this.cameraX, this.cameraY, this.zoom, this.width, this.height);
|
|
175
|
+
}
|
|
176
|
+
const wasm = getLaminaWasm();
|
|
177
|
+
const outPtr = wasm.exports.malloc(16);
|
|
178
|
+
wasm.exports.lamina_wasm_world_to_screen(worldX, worldY, this.cameraX, this.cameraY, this.zoom, this.width, this.height, outPtr);
|
|
179
|
+
const mem = (wasm.exports.memory || getSubstrate().memory).buffer;
|
|
180
|
+
const arr = new Float64Array(mem, outPtr, 2);
|
|
181
|
+
const res = { x: Math.round(arr[0]), y: Math.round(arr[1]) };
|
|
182
|
+
wasm.exports.free(outPtr);
|
|
183
|
+
return res;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
screenToWorld(screenX, screenY) {
|
|
187
|
+
if (nativeBinding && nativeBinding.screenToWorld) {
|
|
188
|
+
return nativeBinding.screenToWorld(screenX, screenY, this.cameraX, this.cameraY, this.zoom, this.width, this.height);
|
|
189
|
+
}
|
|
190
|
+
const wasm = getLaminaWasm();
|
|
191
|
+
const outPtr = wasm.exports.malloc(16);
|
|
192
|
+
wasm.exports.lamina_wasm_screen_to_world(screenX, screenY, this.cameraX, this.cameraY, this.zoom, this.width, this.height, outPtr);
|
|
193
|
+
const mem = (wasm.exports.memory || getSubstrate().memory).buffer;
|
|
194
|
+
const arr = new Float64Array(mem, outPtr, 2);
|
|
195
|
+
const res = { x: Math.round(arr[0]), y: Math.round(arr[1]) };
|
|
196
|
+
wasm.exports.free(outPtr);
|
|
197
|
+
return res;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* FlexNode: CSS-like flexbox UI element tree layout engine
|
|
203
|
+
*/
|
|
204
|
+
export class FlexNode {
|
|
205
|
+
constructor(direction = "column") {
|
|
206
|
+
const wasm = getLaminaWasm();
|
|
207
|
+
const dir = direction === "row" ? 0 : 1;
|
|
208
|
+
this.wasmHandle = wasm.exports.lamina_node_new(dir);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
setSize(w, h) {
|
|
212
|
+
const wasm = getLaminaWasm();
|
|
213
|
+
wasm.exports.lamina_node_set_size(this.wasmHandle, w, h);
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
setPadding(l, t, r, b) {
|
|
218
|
+
const wasm = getLaminaWasm();
|
|
219
|
+
wasm.exports.lamina_node_set_padding(this.wasmHandle, l, t, r, b);
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
setFlex(grow = 0, shrink = 1) {
|
|
224
|
+
const wasm = getLaminaWasm();
|
|
225
|
+
wasm.exports.lamina_node_set_flex(this.wasmHandle, grow, shrink);
|
|
226
|
+
return this;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
addChild(child) {
|
|
230
|
+
const wasm = getLaminaWasm();
|
|
231
|
+
wasm.exports.lamina_node_add_child(this.wasmHandle, child.wasmHandle);
|
|
232
|
+
return this;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
computeLayout(availableWidth, availableHeight, startX = 0, startY = 0) {
|
|
236
|
+
const wasm = getLaminaWasm();
|
|
237
|
+
wasm.exports.lamina_node_compute_layout(this.wasmHandle, startX, startY, availableWidth, availableHeight);
|
|
238
|
+
return this;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
getBounds() {
|
|
242
|
+
const wasm = getLaminaWasm();
|
|
243
|
+
const outPtr = wasm.exports.malloc(32);
|
|
244
|
+
wasm.exports.lamina_node_get_bounds(this.wasmHandle, outPtr);
|
|
245
|
+
const mem = (wasm.exports.memory || getSubstrate().memory).buffer;
|
|
246
|
+
const arr = new Float64Array(mem, outPtr, 4);
|
|
247
|
+
const bounds = { x: arr[0], y: arr[1], width: arr[2], height: arr[3] };
|
|
248
|
+
wasm.exports.free(outPtr);
|
|
249
|
+
return bounds;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
free() {
|
|
253
|
+
if (this.wasmHandle) {
|
|
254
|
+
const wasm = getLaminaWasm();
|
|
255
|
+
wasm.exports.lamina_node_free(this.wasmHandle);
|
|
256
|
+
this.wasmHandle = 0;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function version() {
|
|
262
|
+
return "1.1.0";
|
|
263
|
+
}
|
package/dist/lamina.wasm
ADDED
|
Binary file
|
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const VS_QUAD = `attribute vec2 a_pos;
|
|
2
|
+
attribute vec2 a_uv;
|
|
3
|
+
varying vec2 v_uv;
|
|
4
|
+
void main() {
|
|
5
|
+
v_uv = a_uv;
|
|
6
|
+
gl_Position = vec4(a_pos, 0.0, 1.0);
|
|
7
|
+
}`;
|
|
8
|
+
|
|
9
|
+
const FS_QUAD = `precision mediump float;
|
|
10
|
+
varying vec2 v_uv;
|
|
11
|
+
uniform sampler2D u_tex;
|
|
12
|
+
void main() {
|
|
13
|
+
gl_FragColor = texture2D(u_tex, v_uv);
|
|
14
|
+
}`;
|
|
15
|
+
|
|
16
|
+
function initGlPipeline(gl) {
|
|
17
|
+
try {
|
|
18
|
+
const vs = gl.createShader(gl.VERTEX_SHADER);
|
|
19
|
+
gl.shaderSource(vs, VS_QUAD);
|
|
20
|
+
gl.compileShader(vs);
|
|
21
|
+
|
|
22
|
+
const fs = gl.createShader(gl.FRAGMENT_SHADER);
|
|
23
|
+
gl.shaderSource(fs, FS_QUAD);
|
|
24
|
+
gl.compileShader(fs);
|
|
25
|
+
|
|
26
|
+
const prog = gl.createProgram();
|
|
27
|
+
gl.attachShader(prog, vs);
|
|
28
|
+
gl.attachShader(prog, fs);
|
|
29
|
+
gl.linkProgram(prog);
|
|
30
|
+
|
|
31
|
+
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
gl.useProgram(prog);
|
|
36
|
+
|
|
37
|
+
const verts = new Float32Array([
|
|
38
|
+
-1.0, -1.0, 0.0, 1.0,
|
|
39
|
+
1.0, -1.0, 1.0, 1.0,
|
|
40
|
+
-1.0, 1.0, 0.0, 0.0,
|
|
41
|
+
-1.0, 1.0, 0.0, 0.0,
|
|
42
|
+
1.0, -1.0, 1.0, 1.0,
|
|
43
|
+
1.0, 1.0, 1.0, 0.0
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const vbo = gl.createBuffer();
|
|
47
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
48
|
+
gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW);
|
|
49
|
+
|
|
50
|
+
const aPos = gl.getAttribLocation(prog, "a_pos");
|
|
51
|
+
const aUv = gl.getAttribLocation(prog, "a_uv");
|
|
52
|
+
gl.enableVertexAttribArray(aPos);
|
|
53
|
+
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 16, 0);
|
|
54
|
+
gl.enableVertexAttribArray(aUv);
|
|
55
|
+
gl.vertexAttribPointer(aUv, 2, gl.FLOAT, false, 16, 8);
|
|
56
|
+
|
|
57
|
+
const tex = gl.createTexture();
|
|
58
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
59
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
60
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
61
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
62
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
63
|
+
|
|
64
|
+
return { prog, vbo, tex };
|
|
65
|
+
} catch (e) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* createLaminaRenderer: Shared hardware-accelerated WebGL textured quad presentation engine
|
|
72
|
+
* with automatic fallback to HTML5 2D canvas putImageData.
|
|
73
|
+
*/
|
|
74
|
+
export function createLaminaRenderer(canvasEl) {
|
|
75
|
+
let gl = null;
|
|
76
|
+
let glPipe = null;
|
|
77
|
+
let canvas2d = null;
|
|
78
|
+
|
|
79
|
+
if (canvasEl && typeof canvasEl.getContext === "function") {
|
|
80
|
+
try {
|
|
81
|
+
const opts = { preserveDrawingBuffer: true, alpha: true, antialias: true };
|
|
82
|
+
gl = canvasEl.getContext("webgl2", opts) || canvasEl.getContext("webgl", opts);
|
|
83
|
+
if (gl) {
|
|
84
|
+
glPipe = initGlPipeline(gl);
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {
|
|
87
|
+
gl = null;
|
|
88
|
+
glPipe = null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!gl || !glPipe) {
|
|
92
|
+
gl = null;
|
|
93
|
+
glPipe = null;
|
|
94
|
+
try {
|
|
95
|
+
canvas2d = canvasEl.getContext("2d");
|
|
96
|
+
} catch (e) {
|
|
97
|
+
canvas2d = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
isWebGL() {
|
|
104
|
+
return Boolean(gl && glPipe);
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
render(rgbaBytes, width, height) {
|
|
108
|
+
if (!canvasEl) return;
|
|
109
|
+
|
|
110
|
+
if (gl && glPipe) {
|
|
111
|
+
gl.viewport(0, 0, canvasEl.width || width, canvasEl.height || height);
|
|
112
|
+
gl.useProgram(glPipe.prog);
|
|
113
|
+
gl.bindTexture(gl.TEXTURE_2D, glPipe.tex);
|
|
114
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, rgbaBytes);
|
|
115
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (canvas2d) {
|
|
120
|
+
const clamped = rgbaBytes instanceof Uint8ClampedArray
|
|
121
|
+
? rgbaBytes
|
|
122
|
+
: new Uint8ClampedArray(rgbaBytes.buffer, rgbaBytes.byteOffset, rgbaBytes.byteLength);
|
|
123
|
+
const imgData = typeof ImageData !== "undefined"
|
|
124
|
+
? new ImageData(clamped, width, height)
|
|
125
|
+
: { data: clamped, width, height };
|
|
126
|
+
canvas2d.putImageData(imgData, 0, 0);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
destroy() {
|
|
131
|
+
if (gl && glPipe) {
|
|
132
|
+
try {
|
|
133
|
+
gl.deleteTexture(glPipe.tex);
|
|
134
|
+
gl.deleteBuffer(glPipe.vbo);
|
|
135
|
+
gl.deleteProgram(glPipe.prog);
|
|
136
|
+
} catch (e) {}
|
|
137
|
+
}
|
|
138
|
+
gl = null;
|
|
139
|
+
glPipe = null;
|
|
140
|
+
canvas2d = null;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file lam-cam.h
|
|
3
|
+
* @brief 3D Camera, matrix math, and perspective projection for liblamina.
|
|
4
|
+
*
|
|
5
|
+
* Provides a lightweight 3D pipeline for 2.5D tilted-camera and full 3D views,
|
|
6
|
+
* including 4x4 matrix operations, camera setup, ray-plane unprojecting,
|
|
7
|
+
* ground tiles, and billboard sprites.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#ifndef LAM_CAM_H
|
|
11
|
+
#define LAM_CAM_H
|
|
12
|
+
|
|
13
|
+
#include <stdint.h>
|
|
14
|
+
#include <stdbool.h>
|
|
15
|
+
|
|
16
|
+
#ifdef __cplusplus
|
|
17
|
+
extern "C" {
|
|
18
|
+
#endif
|
|
19
|
+
|
|
20
|
+
/* 4x4 matrix in column-major order (OpenGL compatible) */
|
|
21
|
+
typedef float lam_mat4_t[16];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @brief Initialize matrix to 4x4 identity.
|
|
25
|
+
*/
|
|
26
|
+
void lam_mat4_identity(float m[16]);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @brief Multiply two 4x4 matrices: out = a * b.
|
|
30
|
+
*/
|
|
31
|
+
void lam_mat4_mul(float out[16], const float a[16], const float b[16]);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @brief Apply translation to matrix m.
|
|
35
|
+
*/
|
|
36
|
+
void lam_mat4_translate(float m[16], float x, float y, float z);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @brief Apply scaling to matrix m.
|
|
40
|
+
*/
|
|
41
|
+
void lam_mat4_scale(float m[16], float sx, float sy, float sz);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @brief Rotate matrix m around X axis by angle in radians.
|
|
45
|
+
*/
|
|
46
|
+
void lam_mat4_rotate_x(float m[16], float rad);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @brief Rotate matrix m around Y axis by angle in radians.
|
|
50
|
+
*/
|
|
51
|
+
void lam_mat4_rotate_y(float m[16], float rad);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @brief Rotate matrix m around Z axis by angle in radians.
|
|
55
|
+
*/
|
|
56
|
+
void lam_mat4_rotate_z(float m[16], float rad);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @brief Construct standard perspective projection matrix.
|
|
60
|
+
*/
|
|
61
|
+
void lam_mat4_perspective(float m[16], float fov_rad, float aspect, float near_z, float far_z);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @brief Construct standard orthographic projection matrix.
|
|
65
|
+
*/
|
|
66
|
+
void lam_mat4_ortho(float m[16], float l, float r, float b, float t, float n, float f);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @brief Construct view matrix looking from eye towards target with given up vector.
|
|
70
|
+
*/
|
|
71
|
+
void lam_mat4_look_at(float m[16], const float eye[3], const float target[3], const float up[3]);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @brief Invert a 4x4 matrix. Returns true on success, false if singular.
|
|
75
|
+
*/
|
|
76
|
+
bool lam_mat4_invert(float out[16], const float in[16]);
|
|
77
|
+
|
|
78
|
+
/* Camera state descriptor */
|
|
79
|
+
typedef struct {
|
|
80
|
+
float target[3]; /* Focus point in world coordinates (X, Y, Z) */
|
|
81
|
+
float distance; /* Distance from target */
|
|
82
|
+
float pitch_deg; /* Tilt pitch angle (degrees, e.g. 50.0) */
|
|
83
|
+
float yaw_deg; /* Rotation yaw angle (degrees, 0 = facing north) */
|
|
84
|
+
float fov_deg; /* Vertical FOV (degrees, e.g. 45.0) */
|
|
85
|
+
float near_z; /* Near clipping plane (e.g. 0.1) */
|
|
86
|
+
float far_z; /* Far clipping plane (e.g. 1000.0) */
|
|
87
|
+
int active; /* 1 if 3D camera is currently enabled, 0 for 2D */
|
|
88
|
+
|
|
89
|
+
/* Computed matrices and vectors */
|
|
90
|
+
lam_mat4_t view;
|
|
91
|
+
lam_mat4_t proj;
|
|
92
|
+
lam_mat4_t vp;
|
|
93
|
+
lam_mat4_t inv_vp;
|
|
94
|
+
float eye[3];
|
|
95
|
+
float right[3];
|
|
96
|
+
float up[3];
|
|
97
|
+
} lam_cam_t;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @brief Configure and activate the 3D camera.
|
|
101
|
+
*
|
|
102
|
+
* @param target_x, target_y Target world coordinates on the ground plane
|
|
103
|
+
* @param distance Camera distance / zoom
|
|
104
|
+
* @param pitch_deg Camera tilt angle in degrees (e.g. 45..60)
|
|
105
|
+
* @param yaw_deg Camera yaw rotation angle in degrees
|
|
106
|
+
* @param fov_deg Field of view in degrees (e.g. 45.0)
|
|
107
|
+
*/
|
|
108
|
+
void (lam_cam_set_3d)(float target_x, float target_y, float distance,
|
|
109
|
+
float pitch_deg, float yaw_deg, float fov_deg);
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @brief Restore classic 2D orthographic rendering mode.
|
|
113
|
+
*/
|
|
114
|
+
#undef lam_cam_set_2d
|
|
115
|
+
void lam_cam_set_2d(void);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @brief Check if 3D camera mode is currently active.
|
|
119
|
+
*/
|
|
120
|
+
int lam_cam_is_3d(void);
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @brief Get pointer to active camera state.
|
|
124
|
+
*/
|
|
125
|
+
const lam_cam_t *lam_cam_get(void);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @brief Copy the current View-Projection matrix into out.
|
|
129
|
+
*/
|
|
130
|
+
void lam_cam_get_vp(float out[16]);
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @brief Get the camera right vector in world coordinates.
|
|
134
|
+
*/
|
|
135
|
+
void lam_cam_get_right(float r[3]);
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @brief Cast a ray from screen coordinates and find intersection with ground plane Z=0.
|
|
139
|
+
*
|
|
140
|
+
* @param screen_x, screen_y Screen pixel coordinates
|
|
141
|
+
* @param out_x, out_y Output world coordinates on the ground plane
|
|
142
|
+
* @return 1 on intersection, 0 if parallel or pointing away
|
|
143
|
+
*/
|
|
144
|
+
int lam_unproject_ground(int32_t screen_x, int32_t screen_y, float *out_x, float *out_y);
|
|
145
|
+
|
|
146
|
+
#ifdef __cplusplus
|
|
147
|
+
}
|
|
148
|
+
#endif
|
|
149
|
+
|
|
150
|
+
#endif /* LAM_CAM_H */
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#ifndef LAM_FONT_H
|
|
2
|
+
#define LAM_FONT_H
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file lam-font.h
|
|
6
|
+
* @brief Public header for the liblamina bitmap font system.
|
|
7
|
+
*
|
|
8
|
+
* Declares the API for loading, measuring and rendering
|
|
9
|
+
* bitmap-based fonts used by the liblamina rendering layer.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
#include <stdint.h>
|
|
13
|
+
#include "./lam-ui.h"
|
|
14
|
+
|
|
15
|
+
#ifdef __cplusplus
|
|
16
|
+
extern "C" {
|
|
17
|
+
#endif
|
|
18
|
+
|
|
19
|
+
/** @defgroup lam_font_core liblamina font management
|
|
20
|
+
* @brief Font loading and lifecycle functions.
|
|
21
|
+
* @{
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @brief Load a bitmap font from an image file.
|
|
26
|
+
*
|
|
27
|
+
* Loads a pre-rendered font atlas (e.g. generated from BDF)
|
|
28
|
+
* and registers it in liblamina’s internal font table.
|
|
29
|
+
*
|
|
30
|
+
* @param[in] png_path Path to the font atlas (PNG).
|
|
31
|
+
* @param[in] cell_w Width of each glyph cell.
|
|
32
|
+
* @param[in] cell_h Height of each glyph cell.
|
|
33
|
+
* @param[in] first First character code in the atlas.
|
|
34
|
+
* @param[in] last Last character code in the atlas.
|
|
35
|
+
* @return Font handle (font_ref) or CM_MISS on error.
|
|
36
|
+
*/
|
|
37
|
+
uint32_t lam_font_open(const char *png_path,
|
|
38
|
+
unsigned cell_w,
|
|
39
|
+
unsigned cell_h,
|
|
40
|
+
uint8_t first,
|
|
41
|
+
uint8_t last);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @brief Unload a font and free its resources.
|
|
45
|
+
*
|
|
46
|
+
* @param[in] font_ref Font handle returned by lam_font_open().
|
|
47
|
+
*/
|
|
48
|
+
void lam_font_close(uint32_t font_ref);
|
|
49
|
+
|
|
50
|
+
/** @} */
|
|
51
|
+
|
|
52
|
+
/** @defgroup lam_font_render liblamina font rendering and measurement
|
|
53
|
+
* @brief Functions for drawing and measuring text.
|
|
54
|
+
* @{
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @brief Draw text using a loaded bitmap font.
|
|
59
|
+
*
|
|
60
|
+
* Renders text within the given rectangle, obeying wrapping
|
|
61
|
+
* and breaking rules. Returns a pointer to the first
|
|
62
|
+
* unrendered character if overflow occurred.
|
|
63
|
+
*
|
|
64
|
+
* @param[in] font_ref Font handle.
|
|
65
|
+
* @param[in] text UTF-8 text to render.
|
|
66
|
+
* @param[in] x0,y0 Top-left position (pixels).
|
|
67
|
+
* @param[in] x1,y1 Bottom-right boundary (pixels).
|
|
68
|
+
* @param[in] scale Font scale factor.
|
|
69
|
+
* @param[in] white_space White-space handling mode (see lam_ui_white_space_t).
|
|
70
|
+
* @param[in] word_break Word-break behavior (see lam_ui_word_break_t).
|
|
71
|
+
* @return NULL if fully rendered, or pointer to remaining text.
|
|
72
|
+
*/
|
|
73
|
+
const char *lam_font_draw(uint32_t font_ref,
|
|
74
|
+
const char *text,
|
|
75
|
+
uint32_t x0,
|
|
76
|
+
uint32_t y0,
|
|
77
|
+
uint32_t x1,
|
|
78
|
+
uint32_t y1,
|
|
79
|
+
uint32_t scale,
|
|
80
|
+
lam_ui_white_space_t white_space,
|
|
81
|
+
lam_ui_word_break_t word_break);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @brief Measure text layout and overflow.
|
|
85
|
+
*
|
|
86
|
+
* Computes how text fits within the specified rectangle
|
|
87
|
+
* and returns a pointer to the first unrendered character
|
|
88
|
+
* if overflow occurred.
|
|
89
|
+
*
|
|
90
|
+
* @param[out] out_w Optional: resulting text width in pixels.
|
|
91
|
+
* @param[out] out_h Optional: resulting text height in pixels.
|
|
92
|
+
* @param[in] font_ref Font handle.
|
|
93
|
+
* @param[in] text UTF-8 text to measure.
|
|
94
|
+
* @param[in] x0,y0 Top-left position (pixels).
|
|
95
|
+
* @param[in] x1,y1 Bottom-right boundary (pixels).
|
|
96
|
+
* @param[in] scale Font scale factor.
|
|
97
|
+
* @param[in] white_space White-space handling mode (see lam_ui_white_space_t).
|
|
98
|
+
* @param[in] word_break Word-break behavior (see lam_ui_word_break_t).
|
|
99
|
+
* @return NULL if text fully fits, or pointer to remaining text.
|
|
100
|
+
*/
|
|
101
|
+
const char *lam_font_measure(uint32_t *out_w,
|
|
102
|
+
uint32_t *out_h,
|
|
103
|
+
uint32_t font_ref,
|
|
104
|
+
const char *text,
|
|
105
|
+
uint32_t x0,
|
|
106
|
+
uint32_t y0,
|
|
107
|
+
uint32_t x1,
|
|
108
|
+
uint32_t y1,
|
|
109
|
+
uint32_t scale,
|
|
110
|
+
lam_ui_white_space_t white_space,
|
|
111
|
+
lam_ui_word_break_t word_break);
|
|
112
|
+
|
|
113
|
+
/** @} */
|
|
114
|
+
|
|
115
|
+
#ifdef __cplusplus
|
|
116
|
+
}
|
|
117
|
+
#endif
|
|
118
|
+
|
|
119
|
+
#endif /* LAM_FONT_H */
|