-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
coordinates.ts
105 lines (94 loc) · 3.98 KB
/
coordinates.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import { CameraState, Dimensions } from "../types";
import { identity, multiply, multiplyVec2, rotate, scale, translate } from "./matrices";
/**
* In sigma, the graph is normalized into a [0, 1], [0, 1] square, before being given to the various renderers. This
* helps to deal with quadtree in particular.
* But at some point, we need to rescale it so that it takes the best place in the screen, i.e. we always want to see two
* nodes "touching" opposite sides of the graph, with the camera being at its default state.
*
* This function determines this ratio.
*/
export function getCorrectionRatio(
viewportDimensions: { width: number; height: number },
graphDimensions: { width: number; height: number },
): number {
const viewportRatio = viewportDimensions.height / viewportDimensions.width;
const graphRatio = graphDimensions.height / graphDimensions.width;
// If the stage and the graphs are in different directions (such as the graph being wider that tall while the stage
// is taller than wide), we can stop here to have indeed nodes touching opposite sides:
if ((viewportRatio < 1 && graphRatio > 1) || (viewportRatio > 1 && graphRatio < 1)) {
return 1;
}
// Else, we need to fit the graph inside the stage:
// 1. If the graph is "squarer" (i.e. with a ratio closer to 1), we need to make the largest sides touch;
// 2. If the stage is "squarer", we need to make the smallest sides touch.
return Math.min(Math.max(graphRatio, 1 / graphRatio), Math.max(1 / viewportRatio, viewportRatio));
}
/**
* Function returning a matrix from the current state of the camera.
*/
export function matrixFromCamera(
state: CameraState,
viewportDimensions: { width: number; height: number },
graphDimensions: { width: number; height: number },
padding: number,
inverse?: boolean,
): Float32Array {
// TODO: it's possible to optimize this drastically!
const { angle, ratio, x, y } = state;
const { width, height } = viewportDimensions;
const matrix = identity();
const smallestDimension = Math.min(width, height) - 2 * padding;
const correctionRatio = getCorrectionRatio(viewportDimensions, graphDimensions);
if (!inverse) {
multiply(
matrix,
scale(
identity(),
2 * (smallestDimension / width) * correctionRatio,
2 * (smallestDimension / height) * correctionRatio,
),
);
multiply(matrix, rotate(identity(), -angle));
multiply(matrix, scale(identity(), 1 / ratio));
multiply(matrix, translate(identity(), -x, -y));
} else {
multiply(matrix, translate(identity(), x, y));
multiply(matrix, scale(identity(), ratio));
multiply(matrix, rotate(identity(), angle));
multiply(
matrix,
scale(
identity(),
width / smallestDimension / 2 / correctionRatio,
height / smallestDimension / 2 / correctionRatio,
),
);
}
return matrix;
}
/**
* All these transformations we apply on the matrix to get it rescale the graph
* as we want make it very hard to get pixel-perfect distances in WebGL. This
* function returns a factor that properly cancels the matrix effect on lengths.
*
* [jacomyal]
* To be fully honest, I can't really explain happens here... I notice that the
* following ratio works (i.e. it correctly compensates the matrix impact on all
* camera states I could try):
* > `R = size(V) / size(M * V) / W`
* as long as `M * V` is in the direction of W (ie. parallel to (Ox)). It works
* as well with H and a vector that transforms into something parallel to (Oy).
*
* Also, note that we use `angle` and not `-angle` (that would seem logical,
* since we want to anticipate the rotation), because the image is vertically
* swapped in WebGL.
*/
export function getMatrixImpact(
matrix: Float32Array,
cameraState: CameraState,
viewportDimensions: Dimensions,
): number {
const { x, y } = multiplyVec2(matrix, { x: Math.cos(cameraState.angle), y: Math.sin(cameraState.angle) }, 0);
return 1 / Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) / viewportDimensions.width;
}