Skip to content

Commit f33ffc2

Browse files
committed
Matrix operators and transformations
1 parent 3f69eca commit f33ffc2

7 files changed

Lines changed: 477 additions & 17 deletions

File tree

devlog.md

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +0,0 @@
1-
To start, let me explain what this project is about: we want to render images without the `<img>` and `<canvas>` tags, _and_ without CSS. Because it's hilarious to do so.
2-
3-
The immediate idea that came to mind is transforming an W x H image into a correspoding W x H `<table>` element, where each `<td>` is a 1x1 pixel (or larger, if we want to do make-believe responsivity). A little research showed that this was technically possible, and that's good enough.
4-
Basically, we just have to set the `cellpadding` and `cellspacing` properties of the table element to 0, and all of its `td` elements with whatever we want, and boom: we've got an image!
5-
6-
Now, of course this method will have terrible performance (the DOM wasn't made for rendering thousands of elements, and all drawing is done in the CPU), so we have to do our best to make it usable. My solution, for the time being, is creating the table as a string, and then sending it to the DOM to be parsed and rendered only once. There are alternatives, but we're scraping pennies: actually _drawing_ this to the DOM is the main timesink.
7-
8-
On a sidenote, I forgot how much I disliked doing javascript. Starting out I didn't want to use node or any other "build system", since the project is simple enough, but using raw javascript is a drag (no types, it's hard to link modules to the main file, _lots_ of issue with WebGPU support in vscode, etc). So we're over-engineering this and using node and typescript. I'm all for bad UI, but I'm a little sensitive about my developer experience.
9-
10-
Attached, an [old friend](https://flavortown.hackclub.com/projects/7848), rendered with this method in a 300x300 table.
11-
(Obs: About an hour of work time was lost because I was doing the project in the wrong git repository)
12-
13-
**Commits**
14-
[Commit 49a515b](https://url.jam06452.uk/1jvo9mo): Proof of concept done
15-
[Commit bd885d1](https://url.jam06452.uk/1efmviq): Proof of concept with image data
File renamed without changes.

src/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { build_color_table, build_color_text } from "./cpu/transformer";
2-
import { generate_trash_data } from "./cpu/image";
1+
import { build_color_table, build_color_text } from "./transformer";
2+
import { generate_trash_data } from "./image";
33

44

55
export function main(){

src/math/matrix_operators.ts

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import { ArrayType, mat2, mat3, mat4, vec2, vec3, vec4 } from "./types";
2+
3+
export function dot(u:ArrayType, v:ArrayType):number{
4+
let result = 0;
5+
for(let i = 0; i < u.length; ++i){
6+
result += u[i] * v[i];
7+
}
8+
return result;
9+
}
10+
11+
export function dot_vec2(u:vec2, v:vec2):number{
12+
return u[0] * v[0] + u[1] * v[1];
13+
}
14+
15+
export function dot_vec3(u:vec3, v:vec3):number{
16+
return u[0] * v[0] + u[1] * v[1] + u[2] * v[2];
17+
}
18+
19+
export function cross(u:vec3, v:vec3):vec3{
20+
return vec3(u[1]*v[2] - u[2]*v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]);
21+
}
22+
23+
export function dot_vec4(u:vec4, v:vec4){
24+
return u[0] * v[0] + u[1] * v[1] + u[2] * v[2] + u[3] * v[3];
25+
}
26+
27+
export function mul_mat2(A: mat2, B: mat2): mat2 {
28+
return mat2(
29+
A[0] * B[0] + A[2] * B[1],
30+
A[1] * B[0] + A[3] * B[1],
31+
A[0] * B[2] + A[2] * B[3],
32+
A[1] * B[2] + A[3] * B[3]
33+
);
34+
}
35+
36+
export function mul_mat3(A: mat3, B: mat3): mat3 {
37+
return mat3(
38+
A[0] * B[0] + A[3] * B[1] + A[6] * B[2],
39+
A[1] * B[0] + A[4] * B[1] + A[7] * B[2],
40+
A[2] * B[0] + A[5] * B[1] + A[8] * B[2],
41+
42+
A[0] * B[3] + A[3] * B[4] + A[6] * B[5],
43+
A[1] * B[3] + A[4] * B[4] + A[7] * B[5],
44+
A[2] * B[3] + A[5] * B[4] + A[8] * B[5],
45+
46+
A[0] * B[6] + A[3] * B[7] + A[6] * B[8],
47+
A[1] * B[6] + A[4] * B[7] + A[7] * B[8],
48+
A[2] * B[6] + A[5] * B[7] + A[8] * B[8]
49+
);
50+
}
51+
52+
export function mul_mat4(A: mat4, B: mat4): mat4 {
53+
return mat4(
54+
A[0] * B[0] + A[4] * B[1] + A[8] * B[2] + A[12] * B[3],
55+
A[1] * B[0] + A[5] * B[1] + A[9] * B[2] + A[13] * B[3],
56+
A[2] * B[0] + A[6] * B[1] + A[10] * B[2] + A[14] * B[3],
57+
A[3] * B[0] + A[7] * B[1] + A[11] * B[2] + A[15] * B[3],
58+
59+
A[0] * B[4] + A[4] * B[5] + A[8] * B[6] + A[12] * B[7],
60+
A[1] * B[4] + A[5] * B[5] + A[9] * B[6] + A[13] * B[7],
61+
A[2] * B[4] + A[6] * B[5] + A[10] * B[6] + A[14] * B[7],
62+
A[3] * B[4] + A[7] * B[5] + A[11] * B[6] + A[15] * B[7],
63+
64+
A[0] * B[8] + A[4] * B[9] + A[8] * B[10] + A[12] * B[11],
65+
A[1] * B[8] + A[5] * B[9] + A[9] * B[10] + A[13] * B[11],
66+
A[2] * B[8] + A[6] * B[9] + A[10] * B[10] + A[14] * B[11],
67+
A[3] * B[8] + A[7] * B[9] + A[11] * B[10] + A[15] * B[11],
68+
69+
A[0] * B[12] + A[4] * B[13] + A[8] * B[14] + A[12] * B[15],
70+
A[1] * B[12] + A[5] * B[13] + A[9] * B[14] + A[13] * B[15],
71+
A[2] * B[12] + A[6] * B[13] + A[10] * B[14] + A[14] * B[15],
72+
A[3] * B[12] + A[7] * B[13] + A[11] * B[14] + A[15] * B[15]
73+
);
74+
}
75+
76+
export function mul_mat2_vec2(A: mat2, u: vec2): vec2 {
77+
return vec2(
78+
A[0] * u[0] + A[2] * u[1],
79+
A[1] * u[0] + A[3] * u[1]
80+
);
81+
}
82+
83+
export function mul_mat3_vec3(A: mat3, u: vec3): vec3 {
84+
return vec3(
85+
A[0] * u[0] + A[3] * u[1] + A[6] * u[2],
86+
A[1] * u[0] + A[4] * u[1] + A[7] * u[2],
87+
A[2] * u[0] + A[5] * u[1] + A[8] * u[2]
88+
);
89+
}
90+
91+
export function mul_mat4_vec4(A: mat4, u: vec4): vec4 {
92+
return vec4(
93+
A[0] * u[0] + A[4] * u[1] + A[8] * u[2] + A[12] * u[3],
94+
95+
A[1] * u[0] + A[5] * u[1] + A[9] * u[2] + A[13] * u[3],
96+
97+
A[2] * u[0] + A[6] * u[1] + A[10] * u[2] + A[14] * u[3],
98+
99+
A[3] * u[0] + A[7] * u[1] + A[11] * u[2] + A[15] * u[3]
100+
);
101+
}
102+
103+
export function invert_mat2(A: mat2): mat2 | null {
104+
const a0 = A[0], a1 = A[1], a2 = A[2], a3 = A[3];
105+
let det = a0 * a3 - a2 * a1;
106+
107+
if (!det) {
108+
return null;
109+
}
110+
111+
det = 1.0 / det;
112+
113+
return mat2(
114+
a3 * det,
115+
-a1 * det,
116+
-a2 * det,
117+
a0 * det
118+
);
119+
}
120+
121+
export function invert_mat3(A: mat3): mat3 | null {
122+
const a00 = A[0], a01 = A[1], a02 = A[2];
123+
const a10 = A[3], a11 = A[4], a12 = A[5];
124+
const a20 = A[6], a21 = A[7], a22 = A[8];
125+
126+
const b01 = a22 * a11 - a12 * a21;
127+
const b11 = -a22 * a10 + a12 * a20;
128+
const b21 = a21 * a10 - a11 * a20;
129+
130+
let det = a00 * b01 + a01 * b11 + a02 * b21;
131+
132+
if (!det) {
133+
return null;
134+
}
135+
136+
det = 1.0 / det;
137+
138+
return mat3(
139+
b01 * det,
140+
(-a22 * a01 + a02 * a21) * det,
141+
(a12 * a01 - a02 * a11) * det,
142+
b11 * det,
143+
(a22 * a00 - a02 * a20) * det,
144+
(-a12 * a00 + a02 * a10) * det,
145+
b21 * det,
146+
(-a21 * a00 + a01 * a20) * det,
147+
(a11 * a00 - a01 * a10) * det
148+
);
149+
}
150+
151+
export function invert_mat4(A: mat4): mat4 | null {
152+
const a00 = A[0], a01 = A[1], a02 = A[2], a03 = A[3];
153+
const a10 = A[4], a11 = A[5], a12 = A[6], a13 = A[7];
154+
const a20 = A[8], a21 = A[9], a22 = A[10], a23 = A[11];
155+
const a30 = A[12], a31 = A[13], a32 = A[14], a33 = A[15];
156+
157+
const b00 = a00 * a11 - a01 * a10;
158+
const b01 = a00 * a12 - a02 * a10;
159+
const b02 = a00 * a13 - a03 * a10;
160+
const b03 = a01 * a12 - a02 * a11;
161+
const b04 = a01 * a13 - a03 * a11;
162+
const b05 = a02 * a13 - a03 * a12;
163+
const b06 = a20 * a31 - a21 * a30;
164+
const b07 = a20 * a32 - a22 * a30;
165+
const b08 = a20 * a33 - a23 * a30;
166+
const b09 = a21 * a32 - a22 * a31;
167+
const b10 = a21 * a33 - a23 * a31;
168+
const b11 = a22 * a33 - a23 * a32;
169+
170+
let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
171+
172+
if (!det) {
173+
return null;
174+
}
175+
176+
det = 1.0 / det;
177+
178+
return mat4(
179+
(a11 * b11 - a12 * b10 + a13 * b09) * det,
180+
(a02 * b10 - a01 * b11 - a03 * b09) * det,
181+
(a31 * b05 - a32 * b04 + a33 * b03) * det,
182+
(a22 * b04 - a21 * b05 - a23 * b03) * det,
183+
(a12 * b08 - a10 * b11 - a13 * b07) * det,
184+
(a00 * b11 - a02 * b08 + a03 * b07) * det,
185+
(a32 * b02 - a30 * b05 - a33 * b01) * det,
186+
(a20 * b05 - a22 * b02 + a23 * b01) * det,
187+
(a10 * b10 - a11 * b08 + a13 * b06) * det,
188+
(a01 * b08 - a00 * b10 - a03 * b06) * det,
189+
(a30 * b04 - a31 * b02 + a33 * b00) * det,
190+
(a21 * b02 - a20 * b04 - a23 * b00) * det,
191+
(a11 * b07 - a10 * b09 - a12 * b06) * det,
192+
(a00 * b09 - a01 * b07 + a02 * b06) * det,
193+
(a31 * b01 - a30 * b03 - a32 * b00) * det,
194+
(a20 * b03 - a21 * b01 + a22 * b00) * det
195+
);
196+
}
197+
198+
199+
export function length(v: ArrayType): number {
200+
return Math.sqrt(dot(v,v));
201+
}
202+
203+
export function length_vec3(v:vec3):number{
204+
return Math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
205+
}
206+
207+
export function normalize(v: ArrayType): void {
208+
const len = length(v);
209+
if (len === 0) return;
210+
const inv_len = 1.0 / len;
211+
for(let i = 0; i < v.length; ++i){
212+
v[i] = v[i] * inv_len;
213+
}
214+
}
215+
216+
export function normalize_vec3(v:vec3):void{
217+
const inv_len = 1.0 / length_vec3(v);
218+
v[0] *= inv_len;
219+
v[1] *= inv_len;
220+
v[2] *= inv_len;
221+
}

src/math/transformations.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { mat4, vec3 } from "./types";
2+
import { normalize_vec3, length_vec3, cross, dot_vec3 } from "./matrix_operators";
3+
4+
export function identity(): mat4 {
5+
return mat4(
6+
1, 0, 0, 0,
7+
0, 1, 0, 0,
8+
0, 0, 1, 0,
9+
0, 0, 0, 1
10+
);
11+
}
12+
13+
export function translate(m: mat4, v: vec3): mat4 {
14+
const x = v[0], y = v[1], z = v[2];
15+
const a00 = m[0], a01 = m[1], a02 = m[2], a03 = m[3];
16+
const a10 = m[4], a11 = m[5], a12 = m[6], a13 = m[7];
17+
const a20 = m[8], a21 = m[9], a22 = m[10], a23 = m[11];
18+
const a30 = m[12], a31 = m[13], a32 = m[14], a33 = m[15];
19+
20+
return mat4(
21+
a00, a01, a02, a03,
22+
a10, a11, a12, a13,
23+
a20, a21, a22, a23,
24+
a00 * x + a10 * y + a20 * z + a30,
25+
a01 * x + a11 * y + a21 * z + a31,
26+
a02 * x + a12 * y + a22 * z + a32,
27+
a03 * x + a13 * y + a23 * z + a33
28+
);
29+
}
30+
31+
export function scale(m: mat4, v: vec3): mat4 {
32+
const x = v[0], y = v[1], z = v[2];
33+
return mat4(
34+
m[0] * x, m[1] * x, m[2] * x, m[3] * x,
35+
m[4] * y, m[5] * y, m[6] * y, m[7] * y,
36+
m[8] * z, m[9] * z, m[10] * z, m[11] * z,
37+
m[12], m[13], m[14], m[15]
38+
);
39+
}
40+
41+
export function rotate(m: mat4, angle: number, axis: vec3): mat4 {
42+
if (length_vec3(axis) < 0.000001) { return null as any; }
43+
44+
const n = vec3(axis[0], axis[1], axis[2]);
45+
normalize_vec3(n);
46+
47+
const x = n[0], y = n[1], z = n[2];
48+
const s = Math.sin(angle);
49+
const c = Math.cos(angle);
50+
const t = 1 - c;
51+
52+
const a00 = m[0], a01 = m[1], a02 = m[2], a03 = m[3];
53+
const a10 = m[4], a11 = m[5], a12 = m[6], a13 = m[7];
54+
const a20 = m[8], a21 = m[9], a22 = m[10], a23 = m[11];
55+
56+
const b00 = x * x * t + c;
57+
const b01 = y * x * t + z * s;
58+
const b02 = z * x * t - y * s;
59+
const b10 = x * y * t - z * s;
60+
const b11 = y * y * t + c;
61+
const b12 = z * y * t + x * s;
62+
const b20 = x * z * t + y * s;
63+
const b21 = y * z * t - x * s;
64+
const b22 = z * z * t + c;
65+
66+
return mat4(
67+
a00 * b00 + a10 * b01 + a20 * b02,
68+
a01 * b00 + a11 * b01 + a21 * b02,
69+
a02 * b00 + a12 * b01 + a22 * b02,
70+
a03 * b00 + a13 * b01 + a23 * b02,
71+
72+
a00 * b10 + a10 * b11 + a20 * b12,
73+
a01 * b10 + a11 * b11 + a21 * b12,
74+
a02 * b10 + a12 * b11 + a22 * b12,
75+
a03 * b10 + a13 * b11 + a23 * b12,
76+
77+
a00 * b20 + a10 * b21 + a20 * b22,
78+
a01 * b20 + a11 * b21 + a21 * b22,
79+
a02 * b20 + a12 * b21 + a22 * b22,
80+
a03 * b20 + a13 * b21 + a23 * b22,
81+
82+
m[12], m[13], m[14], m[15]
83+
);
84+
}
85+
86+
export function look_at(eye: vec3, center: vec3, up: vec3): mat4 {
87+
if (Math.abs(eye[0] - center[0]) < 0.000001 &&
88+
Math.abs(eye[1] - center[1]) < 0.000001 &&
89+
Math.abs(eye[2] - center[2]) < 0.000001) {
90+
return identity();
91+
}
92+
93+
const z = vec3(eye[0] - center[0], eye[1] - center[1], eye[2] - center[2]);
94+
normalize_vec3(z);
95+
96+
const x = cross(up, z);
97+
normalize_vec3(x);
98+
if (length_vec3(x) === 0) {
99+
x[0] = 0; x[1] = 0; x[2] = 0;
100+
}
101+
102+
const y = cross(z, x);
103+
normalize_vec3(y);
104+
105+
if (length_vec3(y) === 0) {
106+
y[0] = 0; y[1] = 0; y[2] = 0;
107+
}
108+
109+
return mat4(
110+
x[0], y[0], z[0], 0,
111+
x[1], y[1], z[1], 0,
112+
x[2], y[2], z[2], 0,
113+
-dot_vec3(x, eye),
114+
-dot_vec3(y, eye),
115+
-dot_vec3(z, eye),
116+
1
117+
);
118+
}
119+
120+
export function perspective(fovy: number, aspect: number, near: number, far: number): mat4 {
121+
const f = 1.0 / Math.tan(fovy / 2);
122+
const nf = 1 / (near - far);
123+
124+
return mat4(
125+
f / aspect, 0, 0, 0,
126+
0, f, 0, 0,
127+
0, 0, (far + near) * nf, -1,
128+
0, 0, (2 * far * near) * nf, 0
129+
);
130+
}
131+
132+
export function ortho(left: number, right: number, bottom: number, top: number, near: number, far: number): mat4 {
133+
const lr = 1 / (left - right);
134+
const bt = 1 / (bottom - top);
135+
const nf = 1 / (near - far);
136+
137+
return mat4(
138+
-2 * lr, 0, 0, 0,
139+
0, -2 * bt, 0, 0,
140+
0, 0, 2 * nf, 0,
141+
(left + right) * lr, (top + bottom) * bt, (far + near) * nf, 1
142+
);
143+
}

0 commit comments

Comments
 (0)