@micahdb/tscon 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +44 -2
- package/dist/canvas.d.ts +147 -0
- package/dist/canvas.js +369 -0
- package/dist/colour.d.ts +56 -0
- package/dist/colour.js +77 -0
- package/dist/cp437.d.ts +48 -0
- package/dist/cp437.js +124 -0
- package/dist/glyphs.d.ts +222 -0
- package/dist/glyphs.js +449 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +9 -0
- package/dist/math.d.ts +214 -0
- package/dist/math.js +499 -0
- package/dist/moderndos.d.ts +1 -0
- package/dist/moderndos.js +322 -0
- package/dist/program.d.ts +72 -0
- package/dist/program.js +127 -0
- package/dist/rect.d.ts +26 -0
- package/dist/rect.js +1 -0
- package/dist/renderer.d.ts +59 -0
- package/dist/renderer.js +172 -0
- package/dist/shaders.d.ts +5 -0
- package/dist/shaders.js +470 -0
- package/dist/terminal.d.ts +92 -0
- package/dist/terminal.js +275 -0
- package/dist/texture.d.ts +66 -0
- package/dist/texture.js +119 -0
- package/package.json +44 -5
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Canvas } from "./canvas.ts";
|
|
2
|
+
import type { Glyphs } from "./glyphs.ts";
|
|
3
|
+
import type { Rect } from "./rect.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Renders a character grid from glyphs onto a {@link Canvas}.
|
|
6
|
+
*
|
|
7
|
+
* `Terminal` owns a `rows` x `cols` framebuffer of packed 16-bit glyph
|
|
8
|
+
* cells (as produced by {@link textGlyphs}) and draws it one instanced
|
|
9
|
+
* quad per cell. A frame is typically: {@link clear}, {@link blit} one or
|
|
10
|
+
* more {@link Glyphs} blocks onto the framebuffer, then {@link draw}.
|
|
11
|
+
*
|
|
12
|
+
* If {@link detail_text} is set, it is rendered onto the last row as a
|
|
13
|
+
* status line each frame.
|
|
14
|
+
*/
|
|
15
|
+
export declare class Terminal {
|
|
16
|
+
private gl_program;
|
|
17
|
+
private attributes;
|
|
18
|
+
private uniforms;
|
|
19
|
+
private vao;
|
|
20
|
+
private vbo;
|
|
21
|
+
private fbcon;
|
|
22
|
+
private resized;
|
|
23
|
+
private last_detail_text;
|
|
24
|
+
private detail_glyphs;
|
|
25
|
+
/** The {@link Canvas} this terminal renders into. */
|
|
26
|
+
canvas: Canvas;
|
|
27
|
+
/**
|
|
28
|
+
* Status text shown on the bottom row, or `""` for none.
|
|
29
|
+
*
|
|
30
|
+
* When non-empty it is compiled to glyphs (with a bright-on-black
|
|
31
|
+
* style) and blitted across the last row each {@link draw}. Components
|
|
32
|
+
* use this to show hover details, link URLs, etc.
|
|
33
|
+
*/
|
|
34
|
+
detail_text: string;
|
|
35
|
+
/**
|
|
36
|
+
* Whether to use {@link Canvas.user_font} for non-box glyphs when
|
|
37
|
+
* available. Set to `true` by the application after assigning a font
|
|
38
|
+
* texture to `canvas.user_font`; automatically falls back to the bitmap
|
|
39
|
+
* font if `user_font` is `null`.
|
|
40
|
+
*/
|
|
41
|
+
use_user_font: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Initializes the terminal for use.
|
|
44
|
+
* Listens for `"resize"` events from the canvas.
|
|
45
|
+
*
|
|
46
|
+
* @param canvas The canvas to render into.
|
|
47
|
+
*/
|
|
48
|
+
constructor(canvas: Canvas);
|
|
49
|
+
/**
|
|
50
|
+
* Copy a region of a {@link Glyphs} block onto the console framebuffer.
|
|
51
|
+
*
|
|
52
|
+
* `src` selects the region of `glyphs` to copy and `dst` selects where
|
|
53
|
+
* on the console it goes. `src` and `dst` must be the same size;
|
|
54
|
+
* otherwise the blit is skipped. Both rectangles are clipped to their
|
|
55
|
+
* respective bounds, with negative origins handled by shifting both
|
|
56
|
+
* `src` and `dst` together, so partial off-screen copies work as
|
|
57
|
+
* expected.
|
|
58
|
+
*
|
|
59
|
+
* This only writes to the in-memory framebuffer; call {@link draw} to
|
|
60
|
+
* rasterise it.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* terminal.blit(content,
|
|
65
|
+
* { row: 0, col: 0, rows: content.rows, cols: content.cols },
|
|
66
|
+
* { row: 2, col: 4, rows: content.rows, cols: content.cols });
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @param glyphs Source glyph grid.
|
|
70
|
+
* @param src Region of `glyphs` to copy.
|
|
71
|
+
* @param dst Destination region on the console.
|
|
72
|
+
*/
|
|
73
|
+
blit(glyphs: Glyphs, src: Rect, dst: Rect): void;
|
|
74
|
+
/**
|
|
75
|
+
* Clears the framebuffer. If the canvas has been resized since the
|
|
76
|
+
* last clear, the framebuffer is reallocated to the new `rows` x
|
|
77
|
+
* `cols` size. Resets {@link detail_text} to `""`.
|
|
78
|
+
*
|
|
79
|
+
* Call at the start of each frame, before blitting content.
|
|
80
|
+
*/
|
|
81
|
+
clear(): void;
|
|
82
|
+
/**
|
|
83
|
+
* Rasterise the console framebuffer to the canvas.
|
|
84
|
+
*
|
|
85
|
+
* If {@link detail_text} changed since the last draw it is recompiled
|
|
86
|
+
* to glyphs and blitted across the bottom row. Then the framebuffer
|
|
87
|
+
* is uploaded and drawn.
|
|
88
|
+
*
|
|
89
|
+
* Call this once per frame after all {@link blit} calls are done.
|
|
90
|
+
*/
|
|
91
|
+
draw(): void;
|
|
92
|
+
}
|
package/dist/terminal.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { terminal_frag, terminal_vert } from "./shaders.js";
|
|
2
|
+
import { Canvas } from "./canvas.js";
|
|
3
|
+
import { textGlyphs } from "./glyphs.js";
|
|
4
|
+
import { compileProgram, getAttribLocations, getUniformLocations, } from "./program.js";
|
|
5
|
+
/**
|
|
6
|
+
* Renders a character grid from glyphs onto a {@link Canvas}.
|
|
7
|
+
*
|
|
8
|
+
* `Terminal` owns a `rows` x `cols` framebuffer of packed 16-bit glyph
|
|
9
|
+
* cells (as produced by {@link textGlyphs}) and draws it one instanced
|
|
10
|
+
* quad per cell. A frame is typically: {@link clear}, {@link blit} one or
|
|
11
|
+
* more {@link Glyphs} blocks onto the framebuffer, then {@link draw}.
|
|
12
|
+
*
|
|
13
|
+
* If {@link detail_text} is set, it is rendered onto the last row as a
|
|
14
|
+
* status line each frame.
|
|
15
|
+
*/
|
|
16
|
+
export class Terminal {
|
|
17
|
+
gl_program;
|
|
18
|
+
attributes;
|
|
19
|
+
uniforms;
|
|
20
|
+
vao;
|
|
21
|
+
vbo;
|
|
22
|
+
// framebuffer of console characters
|
|
23
|
+
fbcon;
|
|
24
|
+
resized;
|
|
25
|
+
last_detail_text;
|
|
26
|
+
detail_glyphs;
|
|
27
|
+
/** The {@link Canvas} this terminal renders into. */
|
|
28
|
+
canvas;
|
|
29
|
+
/**
|
|
30
|
+
* Status text shown on the bottom row, or `""` for none.
|
|
31
|
+
*
|
|
32
|
+
* When non-empty it is compiled to glyphs (with a bright-on-black
|
|
33
|
+
* style) and blitted across the last row each {@link draw}. Components
|
|
34
|
+
* use this to show hover details, link URLs, etc.
|
|
35
|
+
*/
|
|
36
|
+
detail_text;
|
|
37
|
+
/**
|
|
38
|
+
* Whether to use {@link Canvas.user_font} for non-box glyphs when
|
|
39
|
+
* available. Set to `true` by the application after assigning a font
|
|
40
|
+
* texture to `canvas.user_font`; automatically falls back to the bitmap
|
|
41
|
+
* font if `user_font` is `null`.
|
|
42
|
+
*/
|
|
43
|
+
use_user_font;
|
|
44
|
+
/**
|
|
45
|
+
* Initializes the terminal for use.
|
|
46
|
+
* Listens for `"resize"` events from the canvas.
|
|
47
|
+
*
|
|
48
|
+
* @param canvas The canvas to render into.
|
|
49
|
+
*/
|
|
50
|
+
constructor(canvas) {
|
|
51
|
+
this.canvas = canvas;
|
|
52
|
+
const gl = canvas.gl;
|
|
53
|
+
this.fbcon = new Uint16Array(0);
|
|
54
|
+
this.last_detail_text = "";
|
|
55
|
+
this.detail_glyphs = null;
|
|
56
|
+
this.detail_text = "";
|
|
57
|
+
this.gl_program = compileProgram(gl, terminal_vert, terminal_frag);
|
|
58
|
+
this.attributes = getAttribLocations(gl, this.gl_program, {
|
|
59
|
+
colour: "a_colour",
|
|
60
|
+
char_code: "a_char_code",
|
|
61
|
+
});
|
|
62
|
+
this.uniforms = getUniformLocations(gl, this.gl_program, {
|
|
63
|
+
rows: "u_rows",
|
|
64
|
+
cols: "u_cols",
|
|
65
|
+
mouse_row: "u_mouse_row",
|
|
66
|
+
mouse_col: "u_mouse_col",
|
|
67
|
+
palette: "u_palette",
|
|
68
|
+
bitmap_font: "u_bitmap_font",
|
|
69
|
+
user_font: "u_user_font",
|
|
70
|
+
use_user_font: "u_use_user_font",
|
|
71
|
+
});
|
|
72
|
+
const vao = gl.createVertexArray();
|
|
73
|
+
this.vao = vao;
|
|
74
|
+
if (!vao) {
|
|
75
|
+
throw new Error("Could not create vertex array object");
|
|
76
|
+
}
|
|
77
|
+
gl.bindVertexArray(vao);
|
|
78
|
+
const vbo = gl.createBuffer();
|
|
79
|
+
this.vbo = vbo;
|
|
80
|
+
if (!vbo) {
|
|
81
|
+
throw new Error("Could not create vertex buffer");
|
|
82
|
+
}
|
|
83
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
84
|
+
// every vertex in the VBO is a 16-bit integer split like so:
|
|
85
|
+
// [ a_colour | a_char_code ]
|
|
86
|
+
// [ 8 bits | 8 bits ]
|
|
87
|
+
// a_colour
|
|
88
|
+
gl.vertexAttribIPointer(this.attributes.colour, 1, gl.UNSIGNED_BYTE, 2, 1);
|
|
89
|
+
gl.vertexAttribDivisor(this.attributes.colour, 1);
|
|
90
|
+
gl.enableVertexAttribArray(this.attributes.colour);
|
|
91
|
+
// a_char_code
|
|
92
|
+
gl.vertexAttribIPointer(this.attributes.char_code, 1, gl.UNSIGNED_BYTE, 2, 0);
|
|
93
|
+
gl.vertexAttribDivisor(this.attributes.char_code, 1);
|
|
94
|
+
gl.enableVertexAttribArray(this.attributes.char_code);
|
|
95
|
+
gl.bindVertexArray(null);
|
|
96
|
+
gl.useProgram(this.gl_program);
|
|
97
|
+
gl.uniform1i(this.uniforms.rows, this.canvas.rows);
|
|
98
|
+
gl.uniform1i(this.uniforms.cols, this.canvas.cols);
|
|
99
|
+
gl.uniform1i(this.uniforms.mouse_row, -1);
|
|
100
|
+
gl.uniform1i(this.uniforms.mouse_col, -1);
|
|
101
|
+
gl.uniform3fv(this.uniforms.palette, this.canvas.palette);
|
|
102
|
+
gl.uniform1i(this.uniforms.bitmap_font, 0);
|
|
103
|
+
gl.uniform1i(this.uniforms.user_font, 1);
|
|
104
|
+
gl.uniform1i(this.uniforms.use_user_font, 0);
|
|
105
|
+
this.resized = true;
|
|
106
|
+
this.use_user_font = false;
|
|
107
|
+
this.canvas.addEventListener("resize", () => {
|
|
108
|
+
this.resized = true;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Copy a region of a {@link Glyphs} block onto the console framebuffer.
|
|
113
|
+
*
|
|
114
|
+
* `src` selects the region of `glyphs` to copy and `dst` selects where
|
|
115
|
+
* on the console it goes. `src` and `dst` must be the same size;
|
|
116
|
+
* otherwise the blit is skipped. Both rectangles are clipped to their
|
|
117
|
+
* respective bounds, with negative origins handled by shifting both
|
|
118
|
+
* `src` and `dst` together, so partial off-screen copies work as
|
|
119
|
+
* expected.
|
|
120
|
+
*
|
|
121
|
+
* This only writes to the in-memory framebuffer; call {@link draw} to
|
|
122
|
+
* rasterise it.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* terminal.blit(content,
|
|
127
|
+
* { row: 0, col: 0, rows: content.rows, cols: content.cols },
|
|
128
|
+
* { row: 2, col: 4, rows: content.rows, cols: content.cols });
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* @param glyphs Source glyph grid.
|
|
132
|
+
* @param src Region of `glyphs` to copy.
|
|
133
|
+
* @param dst Destination region on the console.
|
|
134
|
+
*/
|
|
135
|
+
blit(glyphs, src, dst) {
|
|
136
|
+
// validate the input rects
|
|
137
|
+
if (src.rows !== dst.rows || src.cols !== dst.cols) {
|
|
138
|
+
console.log("Terminal.blit: not blitting due to bad src or dst");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// handle negatives
|
|
142
|
+
if (src.row < 0) {
|
|
143
|
+
const drow = Math.abs(src.row);
|
|
144
|
+
src.row = 0;
|
|
145
|
+
src.rows -= drow;
|
|
146
|
+
dst.row += drow;
|
|
147
|
+
dst.rows -= drow;
|
|
148
|
+
}
|
|
149
|
+
if (src.col < 0) {
|
|
150
|
+
const dcol = Math.abs(src.col);
|
|
151
|
+
src.col = 0;
|
|
152
|
+
src.cols -= dcol;
|
|
153
|
+
dst.col += dcol;
|
|
154
|
+
dst.cols -= dcol;
|
|
155
|
+
}
|
|
156
|
+
if (dst.row < 0) {
|
|
157
|
+
const drow = Math.abs(dst.row);
|
|
158
|
+
src.row += drow;
|
|
159
|
+
src.rows -= drow;
|
|
160
|
+
dst.row = 0;
|
|
161
|
+
dst.rows -= drow;
|
|
162
|
+
}
|
|
163
|
+
if (dst.col < 0) {
|
|
164
|
+
const dcol = Math.abs(dst.col);
|
|
165
|
+
src.col += dcol;
|
|
166
|
+
src.cols -= dcol;
|
|
167
|
+
dst.col = 0;
|
|
168
|
+
dst.cols -= dcol;
|
|
169
|
+
}
|
|
170
|
+
// handle overflow
|
|
171
|
+
if (dst.row + dst.rows > this.canvas.rows) {
|
|
172
|
+
const extra_rows = dst.row + dst.rows -
|
|
173
|
+
this.canvas.rows;
|
|
174
|
+
dst.rows -= extra_rows;
|
|
175
|
+
src.rows = dst.rows;
|
|
176
|
+
}
|
|
177
|
+
if (dst.col + dst.cols > this.canvas.cols) {
|
|
178
|
+
const extra_cols = dst.col + dst.cols -
|
|
179
|
+
this.canvas.cols;
|
|
180
|
+
dst.cols -= extra_cols;
|
|
181
|
+
src.cols = dst.cols;
|
|
182
|
+
}
|
|
183
|
+
for (let row = 0;; row++) {
|
|
184
|
+
const src_row = row + src.row;
|
|
185
|
+
const dst_row = row + dst.row;
|
|
186
|
+
if (src_row >= src.row + src.rows ||
|
|
187
|
+
dst_row >= dst.row + dst.rows) {
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
const src_idx = src_row * glyphs.cols + src.col;
|
|
191
|
+
const dst_idx = dst_row * this.canvas.cols + dst.col;
|
|
192
|
+
this.fbcon.set(glyphs.data.subarray(src_idx, src_idx + src.cols), dst_idx);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Clears the framebuffer. If the canvas has been resized since the
|
|
197
|
+
* last clear, the framebuffer is reallocated to the new `rows` x
|
|
198
|
+
* `cols` size. Resets {@link detail_text} to `""`.
|
|
199
|
+
*
|
|
200
|
+
* Call at the start of each frame, before blitting content.
|
|
201
|
+
*/
|
|
202
|
+
clear() {
|
|
203
|
+
const gl = this.canvas.gl;
|
|
204
|
+
if (this.resized) {
|
|
205
|
+
this.resized = false;
|
|
206
|
+
gl.useProgram(this.gl_program);
|
|
207
|
+
gl.uniform1i(this.uniforms.rows, this.canvas.rows);
|
|
208
|
+
gl.uniform1i(this.uniforms.cols, this.canvas.cols);
|
|
209
|
+
this.fbcon = new Uint16Array(this.canvas.rows * this.canvas.cols);
|
|
210
|
+
}
|
|
211
|
+
this.fbcon.fill(0);
|
|
212
|
+
this.detail_text = "";
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Rasterise the console framebuffer to the canvas.
|
|
216
|
+
*
|
|
217
|
+
* If {@link detail_text} changed since the last draw it is recompiled
|
|
218
|
+
* to glyphs and blitted across the bottom row. Then the framebuffer
|
|
219
|
+
* is uploaded and drawn.
|
|
220
|
+
*
|
|
221
|
+
* Call this once per frame after all {@link blit} calls are done.
|
|
222
|
+
*/
|
|
223
|
+
draw() {
|
|
224
|
+
if (this.last_detail_text !== this.detail_text) {
|
|
225
|
+
this.last_detail_text = this.detail_text;
|
|
226
|
+
if (this.detail_text.length > 0) {
|
|
227
|
+
this.detail_glyphs = textGlyphs("\\B0\\F7" + this.detail_text, this.detail_text.length, false);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
this.detail_glyphs = null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (this.detail_text.length > 0 &&
|
|
234
|
+
this.detail_glyphs !== null) {
|
|
235
|
+
this.blit(this.detail_glyphs, {
|
|
236
|
+
row: 0,
|
|
237
|
+
col: 0,
|
|
238
|
+
rows: 1,
|
|
239
|
+
cols: this.detail_glyphs.cols,
|
|
240
|
+
}, {
|
|
241
|
+
row: this.canvas.rows - 1,
|
|
242
|
+
col: 0,
|
|
243
|
+
rows: 1,
|
|
244
|
+
cols: this.detail_glyphs.cols,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const gl = this.canvas.gl;
|
|
248
|
+
gl.useProgram(this.gl_program);
|
|
249
|
+
// set mouse position uniforms
|
|
250
|
+
gl.uniform1i(this.uniforms.mouse_row, this.canvas.mouse_row ?? -1);
|
|
251
|
+
gl.uniform1i(this.uniforms.mouse_col, this.canvas.mouse_col ?? -1);
|
|
252
|
+
// upload glyph data to the GPU
|
|
253
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
|
|
254
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.fbcon, gl.DYNAMIC_DRAW);
|
|
255
|
+
// bind and activate the bitmap font
|
|
256
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
257
|
+
gl.bindTexture(gl.TEXTURE_2D, this.canvas.bitmap_font);
|
|
258
|
+
// user application-provided font
|
|
259
|
+
if (this.use_user_font) {
|
|
260
|
+
gl.uniform1i(this.uniforms.use_user_font, 1);
|
|
261
|
+
}
|
|
262
|
+
if (this.canvas.user_font !== null) {
|
|
263
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
264
|
+
gl.bindTexture(gl.TEXTURE_2D, this.canvas.user_font);
|
|
265
|
+
}
|
|
266
|
+
else if (this.use_user_font) {
|
|
267
|
+
this.use_user_font = false;
|
|
268
|
+
gl.uniform1i(this.uniforms.use_user_font, 0);
|
|
269
|
+
}
|
|
270
|
+
// draw glyphs: generate 6 vertices (two triangles = one quad) per glyph instance
|
|
271
|
+
gl.bindVertexArray(this.vao);
|
|
272
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, this.canvas.rows * this.canvas.cols);
|
|
273
|
+
gl.bindVertexArray(null);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load an image file from `path` into a mipmapped, linearly-filtered WebGL2
|
|
3
|
+
* texture.
|
|
4
|
+
*
|
|
5
|
+
* The image is decoded via an `Image` element, uploaded to the GL context as
|
|
6
|
+
* an `RGBA`/`UNSIGNED_BYTE` texture with premultiplied alpha, and given
|
|
7
|
+
* `LINEAR_MIPMAP_LINEAR` minification and `LINEAR` magnification filtering so
|
|
8
|
+
* it can be sampled at arbitrary sizes by a shader.
|
|
9
|
+
*
|
|
10
|
+
* The returned texture is suitable for use as the `texture` argument of
|
|
11
|
+
* {@link Renderer.draw} (e.g. a photograph, portrait or rendered scene) or
|
|
12
|
+
* as the `canvas.user_font` application-provided font texture.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { Canvas, loadTexture } from "@micahdb/tscon";
|
|
17
|
+
*
|
|
18
|
+
* const canvas = new Canvas(element);
|
|
19
|
+
* const texture = await loadTexture(canvas.gl, "/images/portrait.jpeg");
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @param gl The WebGL2 context to create the texture on.
|
|
23
|
+
* @param path URL/path of the image to load.
|
|
24
|
+
* @returns A promise that resolves with the created `WebGLTexture` once
|
|
25
|
+
* the image has been decoded and uploaded.
|
|
26
|
+
*/
|
|
27
|
+
export declare function loadTexture(gl: WebGL2RenderingContext, path: string): Promise<WebGLTexture>;
|
|
28
|
+
/**
|
|
29
|
+
* Load six images into a mipmapped cube-map texture.
|
|
30
|
+
*
|
|
31
|
+
* Cube maps are used for environment/skybox rendering. The six faces must be
|
|
32
|
+
* supplied in the standard OpenGL cube-map face order:
|
|
33
|
+
*
|
|
34
|
+
* 1. positive X (right)
|
|
35
|
+
* 2. negative X (left)
|
|
36
|
+
* 3. positive Y (top)
|
|
37
|
+
* 4. negative Y (bottom)
|
|
38
|
+
* 5. positive Z (front)
|
|
39
|
+
* 6. negative Z (back)
|
|
40
|
+
*
|
|
41
|
+
* The resulting texture uses `LINEAR_MIPMAP_LINEAR` / `LINEAR` filtering and is
|
|
42
|
+
* suitable for sampling with `samplerCube` in a shader.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* import { Canvas, loadCubeMap } from "@micahdb/tscon";
|
|
47
|
+
*
|
|
48
|
+
* const canvas = new Canvas(element);
|
|
49
|
+
* const skybox = await loadCubeMap(canvas.gl, [
|
|
50
|
+
* "/images/skybox/right.png",
|
|
51
|
+
* "/images/skybox/left.png",
|
|
52
|
+
* "/images/skybox/top.png",
|
|
53
|
+
* "/images/skybox/bottom.png",
|
|
54
|
+
* "/images/skybox/front.png",
|
|
55
|
+
* "/images/skybox/back.png",
|
|
56
|
+
* ]);
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param gl The WebGL2 context to create the texture on.
|
|
60
|
+
* @param faces Exactly six image URLs, one per cube-map face
|
|
61
|
+
* (see order above).
|
|
62
|
+
* @returns A promise that resolves with the created cube-map texture.
|
|
63
|
+
* @throws If `faces` does not contain exactly six entries or the GL
|
|
64
|
+
* texture cannot be created.
|
|
65
|
+
*/
|
|
66
|
+
export declare function loadCubeMap(gl: WebGL2RenderingContext, faces: string[]): Promise<WebGLTexture>;
|
package/dist/texture.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load an image file from `path` into a mipmapped, linearly-filtered WebGL2
|
|
3
|
+
* texture.
|
|
4
|
+
*
|
|
5
|
+
* The image is decoded via an `Image` element, uploaded to the GL context as
|
|
6
|
+
* an `RGBA`/`UNSIGNED_BYTE` texture with premultiplied alpha, and given
|
|
7
|
+
* `LINEAR_MIPMAP_LINEAR` minification and `LINEAR` magnification filtering so
|
|
8
|
+
* it can be sampled at arbitrary sizes by a shader.
|
|
9
|
+
*
|
|
10
|
+
* The returned texture is suitable for use as the `texture` argument of
|
|
11
|
+
* {@link Renderer.draw} (e.g. a photograph, portrait or rendered scene) or
|
|
12
|
+
* as the `canvas.user_font` application-provided font texture.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { Canvas, loadTexture } from "@micahdb/tscon";
|
|
17
|
+
*
|
|
18
|
+
* const canvas = new Canvas(element);
|
|
19
|
+
* const texture = await loadTexture(canvas.gl, "/images/portrait.jpeg");
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @param gl The WebGL2 context to create the texture on.
|
|
23
|
+
* @param path URL/path of the image to load.
|
|
24
|
+
* @returns A promise that resolves with the created `WebGLTexture` once
|
|
25
|
+
* the image has been decoded and uploaded.
|
|
26
|
+
*/
|
|
27
|
+
export function loadTexture(gl, path) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const image = new Image();
|
|
30
|
+
image.src = path;
|
|
31
|
+
image.onload = () => {
|
|
32
|
+
const tex = gl.createTexture();
|
|
33
|
+
if (!tex) {
|
|
34
|
+
reject(new Error("When creating GL texture"));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
38
|
+
// prevent texture halos/outlines from filtering
|
|
39
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
|
40
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
41
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
42
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
|
43
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
44
|
+
resolve(tex);
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Load six images into a mipmapped cube-map texture.
|
|
50
|
+
*
|
|
51
|
+
* Cube maps are used for environment/skybox rendering. The six faces must be
|
|
52
|
+
* supplied in the standard OpenGL cube-map face order:
|
|
53
|
+
*
|
|
54
|
+
* 1. positive X (right)
|
|
55
|
+
* 2. negative X (left)
|
|
56
|
+
* 3. positive Y (top)
|
|
57
|
+
* 4. negative Y (bottom)
|
|
58
|
+
* 5. positive Z (front)
|
|
59
|
+
* 6. negative Z (back)
|
|
60
|
+
*
|
|
61
|
+
* The resulting texture uses `LINEAR_MIPMAP_LINEAR` / `LINEAR` filtering and is
|
|
62
|
+
* suitable for sampling with `samplerCube` in a shader.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* import { Canvas, loadCubeMap } from "@micahdb/tscon";
|
|
67
|
+
*
|
|
68
|
+
* const canvas = new Canvas(element);
|
|
69
|
+
* const skybox = await loadCubeMap(canvas.gl, [
|
|
70
|
+
* "/images/skybox/right.png",
|
|
71
|
+
* "/images/skybox/left.png",
|
|
72
|
+
* "/images/skybox/top.png",
|
|
73
|
+
* "/images/skybox/bottom.png",
|
|
74
|
+
* "/images/skybox/front.png",
|
|
75
|
+
* "/images/skybox/back.png",
|
|
76
|
+
* ]);
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @param gl The WebGL2 context to create the texture on.
|
|
80
|
+
* @param faces Exactly six image URLs, one per cube-map face
|
|
81
|
+
* (see order above).
|
|
82
|
+
* @returns A promise that resolves with the created cube-map texture.
|
|
83
|
+
* @throws If `faces` does not contain exactly six entries or the GL
|
|
84
|
+
* texture cannot be created.
|
|
85
|
+
*/
|
|
86
|
+
export async function loadCubeMap(gl, faces) {
|
|
87
|
+
if (faces.length !== 6) {
|
|
88
|
+
throw new Error("Cube map requires exactly 6 faces");
|
|
89
|
+
}
|
|
90
|
+
const texture = gl.createTexture();
|
|
91
|
+
if (!texture) {
|
|
92
|
+
throw new Error("When creating cube map texture");
|
|
93
|
+
}
|
|
94
|
+
gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
|
|
95
|
+
const targets = [
|
|
96
|
+
gl.TEXTURE_CUBE_MAP_POSITIVE_X,
|
|
97
|
+
gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
|
|
98
|
+
gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
|
|
99
|
+
gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
|
|
100
|
+
gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
|
|
101
|
+
gl.TEXTURE_CUBE_MAP_NEGATIVE_Z,
|
|
102
|
+
];
|
|
103
|
+
const promises = faces.map(async (path, index) => {
|
|
104
|
+
const image = new Promise((resolve, reject) => {
|
|
105
|
+
const img = new Image();
|
|
106
|
+
img.src = path;
|
|
107
|
+
img.onload = () => resolve(img);
|
|
108
|
+
img.onerror = (err) => reject(new Error(`When loading image at ${path}`, { cause: err }));
|
|
109
|
+
});
|
|
110
|
+
const img = await image;
|
|
111
|
+
gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
|
|
112
|
+
gl.texImage2D(targets[index], 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
|
|
113
|
+
});
|
|
114
|
+
await Promise.all(promises);
|
|
115
|
+
gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
|
|
116
|
+
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
|
|
117
|
+
gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
118
|
+
return texture;
|
|
119
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@micahdb/tscon",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "WebGL 2.0 text-mode graphics in TypeScript.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Micah Baker",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/micahdbak/tscon.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"webgl",
|
|
13
|
+
"webgl2",
|
|
14
|
+
"text-mode",
|
|
15
|
+
"terminal",
|
|
16
|
+
"console",
|
|
17
|
+
"cp437",
|
|
18
|
+
"typography",
|
|
19
|
+
"canvas"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"lint": "oxlint src scripts",
|
|
37
|
+
"shaders": "bun run scripts/shaders.ts",
|
|
38
|
+
"build": "tsc -p tsconfig.json",
|
|
39
|
+
"prepack": "bun run build"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"oxlint": "^1.88.0",
|
|
43
|
+
"typescript": "^7.0.2"
|
|
44
|
+
}
|
|
45
|
+
}
|