Skip to content

Commit 994e9be

Browse files
committed
move the initFromModelMatrix process docs into a jsdoc tutorial
1 parent b700487 commit 994e9be

File tree

2 files changed

+203
-85
lines changed

2 files changed

+203
-85
lines changed

docs/Rectangle-AABB-Matrix.md

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# Rectangle AABB Matrix
2+
3+
Initialize a Rectangle to a 1 unit square centered at 0 x 0 transformed by a model matrix.
4+
5+
-----
6+
7+
Every drawable is a 1 x 1 unit square that is rotated by its direction, scaled by its skin size and scale, and offset by its rotation center and position. The square representation is made up of 4 points that are transformed by the drawable properties. Often we want a shape that simplifies those 4 points into a non-rotated shape, a axis aligned bounding box.
8+
9+
One approach is to compare the x and y components of each transformed vector and find the minimum and maximum x component and the minimum and maximum y component.
10+
11+
We can start from this approach and determine an alternative one that prodcues the same output with less work.
12+
13+
Starting with transforming one point, here is a 3D point, `v`, transformation by a matrix, `m`.
14+
15+
```js
16+
const v0 = v[0];
17+
const v1 = v[1];
18+
const v2 = v[2];
19+
20+
const d = v0 * m[(0 * 4) + 3] + v1 * m[(1 * 4) + 3] + v2 * m[(2 * 4) + 3] + m[(3 * 4) + 3];
21+
dst[0] = (v0 * m[(0 * 4) + 0] + v1 * m[(1 * 4) + 0] + v2 * m[(2 * 4) + 0] + m[(3 * 4) + 0]) / d;
22+
dst[1] = (v0 * m[(0 * 4) + 1] + v1 * m[(1 * 4) + 1] + v2 * m[(2 * 4) + 1] + m[(3 * 4) + 1]) / d;
23+
dst[2] = (v0 * m[(0 * 4) + 2] + v1 * m[(1 * 4) + 2] + v2 * m[(2 * 4) + 2] + m[(3 * 4) + 2]) / d;
24+
```
25+
26+
As this is a 2D rectangle we can cancel out the third dimension, and the determinant, 'd'.
27+
28+
```js
29+
const v0 = v[0];
30+
const v1 = v[1];
31+
32+
dst = [
33+
v0 * m[(0 * 4) + 0] + v1 * m[(1 * 4) + 0] + m[(3 * 4) + 0,
34+
v0 * m[(0 * 4) + 1] + v1 * m[(1 * 4) + 1] + m[(3 * 4) + 1
35+
];
36+
```
37+
38+
Let's set the matrix points to shorter names for convenience.
39+
40+
```js
41+
const m00 = m[(0 * 4) + 0];
42+
const m01 = m[(0 * 4) + 1];
43+
const m10 = m[(1 * 4) + 0];
44+
const m11 = m[(1 * 4) + 1];
45+
const m30 = m[(3 * 4) + 0];
46+
const m31 = m[(3 * 4) + 1];
47+
```
48+
49+
We need 4 points with positive and negative 0.5 values so the square has sides of length 1.
50+
51+
```js
52+
let p = [0.5, 0.5];
53+
let q = [-0.5, 0.5];
54+
let r = [-0.5, -0.5];
55+
let s = [0.5, -0.5];
56+
```
57+
58+
Transform the points by the matrix.
59+
60+
```js
61+
p = [
62+
0.5 * m00 + 0.5 * m10 + m30,
63+
0.5 * m01 + 0.5 * m11 + m31
64+
];
65+
q = [
66+
-0.5 * m00 + -0.5 * m10 + m30,
67+
0.5 * m01 + 0.5 * m11 + m31
68+
];
69+
r = [
70+
-0.5 * m00 + -0.5 * m10 + m30,
71+
-0.5 * m01 + -0.5 * m11 + m31
72+
];
73+
s = [
74+
0.5 * m00 + 0.5 * m10 + m30,
75+
-0.5 * m01 + -0.5 * m11 + m31
76+
];
77+
```
78+
79+
With 4 transformed points we can build the left, right, top, and bottom values for the Rectangle. Each will use the minimum or the maximum of one of the components of all points.
80+
81+
```js
82+
const left = Math.min(p[0], q[0], r[0], s[0]);
83+
const right = Math.max(p[0], q[0], r[0], s[0]);
84+
const top = Math.max(p[1], q[1], r[1], s[1]);
85+
const bottom = Math.min(p[1], q[1], r[1], s[1]);
86+
```
87+
88+
Fill those calls with the vector expressions.
89+
90+
```js
91+
const left = Math.min(
92+
0.5 * m00 + 0.5 * m10 + m30,
93+
-0.5 * m00 + 0.5 * m10 + m30,
94+
-0.5 * m00 + -0.5 * m10 + m30,
95+
0.5 * m00 + -0.5 * m10 + m30
96+
);
97+
const right = Math.max(
98+
0.5 * m00 + 0.5 * m10 + m30,
99+
-0.5 * m00 + 0.5 * m10 + m30,
100+
-0.5 * m00 + -0.5 * m10 + m30,
101+
0.5 * m00 + -0.5 * m10 + m30
102+
);
103+
const top = Math.max(
104+
0.5 * m01 + 0.5 * m11 + m31,
105+
-0.5 * m01 + 0.5 * m11 + m31,
106+
-0.5 * m01 + -0.5 * m11 + m31,
107+
0.5 * m01 + -0.5 * m11 + m31
108+
);
109+
const bottom = Math.min(
110+
0.5 * m01 + 0.5 * m11 + m31,
111+
-0.5 * m01 + 0.5 * m11 + m31,
112+
-0.5 * m01 + -0.5 * m11 + m31,
113+
0.5 * m01 + -0.5 * m11 + m31
114+
);
115+
```
116+
117+
Pull out the `0.5 * m??` patterns.
118+
119+
```js
120+
const x0 = 0.5 * m00;
121+
const x1 = 0.5 * m10;
122+
const y0 = 0.5 * m01;
123+
const y1 = 0.5 * m11;
124+
125+
const left = Math.min(x0 + x1 + m30, -x0 + x1 + m30, -x0 + -x1 + m30, x0 + -x1 + m30);
126+
const right = Math.max(x0 + x1 + m30, -x0 + x1 + m30, -x0 + -x1 + m30, x0 + -x1 + m30);
127+
const top = Math.max(y0 + y1 + m31, -y0 + y1 + m31, -y0 + -y1 + m31, y0 + -y1 + m31);
128+
const bottom = Math.min(y0 + y1 + m31, -y0 + y1 + m31, -y0 + -y1 + m31, y0 + -y1 + m31);
129+
```
130+
131+
Now each argument for the min and max calls take an expression like `(a * x0 + b * x1 + m3?)`. As each expression has the x0, x1, and m3? variables we can split the min and max calls on the addition operators. Each new call has all the coefficients of that variable.
132+
133+
```js
134+
const left = Math.min(x0, -x0) + Math.min(x1, -x1) + Math.min(m30, m30);
135+
const right = Math.max(x0, -x0) + Math.max(x1, -x1) + Math.max(m30, m30);
136+
const top = Math.max(y0, -y0) + Math.max(y1, -y1) + Math.max(m31, m31);
137+
const bottom = Math.min(y0, -y0) + Math.min(y1, -y1) + Math.min(m31, m31);
138+
```
139+
140+
The min or max of two copies of the same value will just be that value.
141+
142+
```js
143+
const left = Math.min(x0, -x0) + Math.min(x1, -x1) + m30;
144+
const right = Math.max(x0, -x0) + Math.max(x1, -x1) + m30;
145+
const top = Math.max(y0, -y0) + Math.max(y1, -y1) + m31;
146+
const bottom = Math.min(y0, -y0) + Math.min(y1, -y1) + m31;
147+
```
148+
149+
The max of a negative and positive variable will be the absolute value of that variable. The min of a negative and positive variable will the negated absolute value of that variable.
150+
151+
```js
152+
const left = -Math.abs(x0) + -Math.abs(x1) + m30;
153+
const right = Math.abs(x0) + Math.abs(x1) + m30;
154+
const top = Math.abs(y0) + Math.abs(y1) + m31;
155+
const bottom = -Math.abs(y0) + -Math.abs(y1) + m31;
156+
```
157+
158+
Pulling out the negations of the absolute values, left and right as well as top and bottom are the positive or negative sum of the absolute value of the saled and rotated unit value.
159+
160+
```js
161+
const left = -(Math.abs(x0) + Math.abs(x1)) + m30;
162+
const right = Math.abs(x0) + Math.abs(x1) + m30;
163+
const top = Math.abs(y0) + Math.abs(y1) + m31;
164+
const bottom = -(Math.abs(y0) + Math.abs(y1)) + m31;
165+
```
166+
167+
We call pull out those sums and use them twice.
168+
169+
```js
170+
const x = Math.abs(x0) + Math.abs(x1);
171+
const y = Math.abs(y0) + Math.abs(y1);
172+
173+
const left = -x + m30;
174+
const right = x + m30;
175+
const top = y + m31;
176+
const bottom = -y + m31;
177+
```
178+
179+
This lets us arrive at our goal. Inlining some of our variables we get this block that will initialize a Rectangle to a unit square transformed by a matrix.
180+
181+
```js
182+
const m30 = m[(3 * 4) + 0];
183+
const m31 = m[(3 * 4) + 1];
184+
185+
const x = Math.abs(0.5 * m[(0 * 4) + 0]) + Math.abs(0.5 * m[(1 * 4) + 0]);
186+
const y = Math.abs(0.5 * m[(0 * 4) + 1]) + Math.abs(0.5 * m[(1 * 4) + 1]);
187+
188+
const left = -x + m30;
189+
const right = x + m30;
190+
const top = y + m31;
191+
const bottom = -y + m31;
192+
```

src/Rectangle.js

Lines changed: 11 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -55,98 +55,24 @@ class Rectangle {
5555
}
5656

5757
/**
58-
* Initialize a Rectangle to a 1 unit square transformed by a model matrix.
58+
* Initialize a Rectangle to a 1 unit square centered at 0 x 0 transformed
59+
* by a model matrix.
5960
* @param {Array.<number>} m A 4x4 matrix to transform the rectangle by.
61+
* @tutorial Rectangle-AABB-Matrix
6062
*/
6163
initFromModelMatrix (m) {
62-
// Treat this function like we are transforming a vector with each
63-
// component set to 0.5 by a matrix m.
64-
// const v0 = 0.5;
65-
// const v1 = 0.5;
66-
// const v2 = 0.5;
67-
68-
// Of the matrix to do this in 2D space, instead of the 3D provided by
69-
// the matrix, we need the 2x2 "top left" that represents the scale and
70-
// rotation ...
71-
const m00 = m[(0 * 4) + 0];
72-
const m01 = m[(0 * 4) + 1];
73-
const m10 = m[(1 * 4) + 0];
74-
const m11 = m[(1 * 4) + 1];
75-
// ... and the 1x2 "top right" that represents position.
64+
// In 2D space, we will soon use the 2x2 "top left" scale and rotation
65+
// submatrix, while we store and the 1x2 "top right" that position
66+
// vector.
7667
const m30 = m[(3 * 4) + 0];
7768
const m31 = m[(3 * 4) + 1];
7869

79-
// This is how we would normally transform the vector by the matrix.
80-
// var determinant = v0 * m03 + v1 * m13 + v2 * m23 + m33;
81-
// dst[0] = (v0 * m00 + v1 * m10 + v2 * m20 + m30) / determinant;
82-
// dst[1] = (v0 * m01 + v1 * m11 + v2 * m21 + m31) / determinant;
83-
// dst[2] = (v0 * m02 + v1 * m12 + v2 * m22 + m32) / determinant;
84-
85-
// We can skip the v2 multiplications and the determinant.
86-
87-
// Alternatively done with 4 vectors, those vectors would be reflected
88-
// on the x and y axis. We can build those 4 vectors by transforming the
89-
// parts of one vector and reflecting them on the axises after
90-
// multiplication.
91-
92-
// const x0 = 0.5 * m00;
93-
// const x1 = 0.5 * m10;
94-
// const y0 = 0.5 * m01;
95-
// const y1 = 0.5 * m11;
96-
97-
// const p0x = x0 + x1;
98-
// const p0y = y0 + y1;
99-
// const p1x = -x0 + x1;
100-
// const p1y = -y0 + y1;
101-
// const p2x = -x0 + -x1;
102-
// const p2y = -y0 + -y1;
103-
// const p3x = x0 + -x1;
104-
// const p3y = y0 + -y1;
105-
106-
// Since we want to reduce those 4 points to a min and max for each
107-
// axis, we can use those multiplied components to build the min and max
108-
// values without comparing the points.
109-
110-
// We can start by getting the min and max for each of all the points.
111-
// const left = Math.min(x0 + x1, -x0 + x1, -x0 + -x1, x0 + -x1);
112-
// const right = Math.max(x0 + x1, -x0 + x1, -x0 + -x1, x0 + -x1);
113-
// const top = Math.max(y0 + y1, -y0 + y1, -y0 + -y1, y0 + -y1);
114-
// const bottom = Math.min(y0 + y1, -y0 + y1, -y0 + -y1, y0 + -y1);
115-
116-
// Each of those can be replaced with min and max operations on the 0
117-
// and 1 matrix output components.
118-
// const left = Math.min(x0, -x0) + Math.min(x1, -x1);
119-
// const right = Math.max(x0, -x0) + Math.max(x1, -x1);
120-
// const top = Math.max(y0, -y0) + Math.max(y1, -y1);
121-
// const bottom = Math.min(y0, -y0) + Math.min(y1, -y1);
122-
123-
// And they can be replaced with absolute values.
124-
// const left = -Math.abs(x0) + -Math.abs(x1);
125-
// const right = Math.abs(x0) + Math.abs(x1);
126-
// const top = Math.abs(y0) + Math.abs(y1);
127-
// const bottom = -Math.abs(y0) + -Math.abs(y1);
128-
129-
// And those with positive and negative sums of the absolute values.
130-
// const left = -(Math.abs(x0) + Math.abs(x1));
131-
// const right = +(Math.abs(x0) + Math.abs(x1));
132-
// const top = +(Math.abs(y0) + Math.abs(y1));
133-
// const bottom = -(Math.abs(y0) + -Math.abs(y1));
134-
135-
// We can perform those sums once and reuse them for the bounds.
136-
// const x = Math.abs(x0) + Math.abs(x1);
137-
// const y = Math.abs(y0) + Math.abs(y1);
138-
// const left = -x;
139-
// const right = x;
140-
// const top = y;
141-
// const bottom = -y;
142-
143-
// Building those absolute sums for the 0.5 vector components by the
144-
// matrix components ...
145-
const x = Math.abs(0.5 * m00) + Math.abs(0.5 * m10);
146-
const y = Math.abs(0.5 * m01) + Math.abs(0.5 * m11);
70+
// "Transform" a (0.5, 0.5) vector by the scale and rotation matrix but
71+
// sum the absolute of each component instead of use the signed values.
72+
const x = Math.abs(0.5 * m[(0 * 4) + 0]) + Math.abs(0.5 * m[(1 * 4) + 0]);
73+
const y = Math.abs(0.5 * m[(0 * 4) + 1]) + Math.abs(0.5 * m[(1 * 4) + 1]);
14774

148-
// And adding them to the position components in the matrices
149-
// initializes our Rectangle.
75+
// And adding them to the position components initializes our Rectangle.
15076
this.left = -x + m30;
15177
this.right = x + m30;
15278
this.top = y + m31;

0 commit comments

Comments
 (0)