]>
Commit | Line | Data |
---|---|---|
beaf2913 BB |
1 | // App Config |
2 | ||
3 | const kEntrypoint = "breadsticks"; | |
4 | const kGap = 50; | |
5 | const kDrawDelay = 100; | |
6 | const kDarkDrawingColor = '#fff'; | |
7 | const kLightDrawingColor = '#000'; | |
97d30c50 BB |
8 | const kMaxVectorMagnitude = 150; // max size of a vector |
9 | const kMagnitudeColorMultiplier = 2.5; // how large should the magnitude be before it's completely transparent | |
beaf2913 BB |
10 | |
11 | /*********************************************** | |
12 | * Breadstick generates a matrix of points | |
13 | * and assigns a vector to each entry | |
14 | * and then draws them | |
15 | ***********************************************/ | |
16 | ||
17 | /* | |
18 | * Generate field of points in which to draw | |
19 | */ | |
20 | ||
21 | // Updates the field object using a canvas | |
22 | ||
23 | const updateFieldFromCanvas = (field, canvas) => { | |
24 | updateFieldFromRect(field, canvas.getBoundingClientRect()); | |
25 | ||
26 | canvas.width = window.innerWidth; | |
27 | canvas.height = window.innerHeight; | |
28 | ||
29 | window.addEventListener('resize', () => { | |
30 | canvas.width = window.innerWidth; | |
31 | canvas.height = window.innerHeight; | |
32 | updateFieldFromRect(field, canvas.getBoundingClientRect()); | |
33 | }); | |
34 | }; | |
35 | ||
36 | // Updates a field object using a rect | |
37 | ||
38 | const updateFieldFromRect = (field, rect) => { | |
39 | field.width = rect.width; | |
40 | field.height = rect.height; | |
41 | } | |
42 | ||
43 | /* | |
44 | * Generate the matrix of vectors | |
45 | */ | |
46 | ||
47 | // Updates a matrix for a given field using the mouse | |
48 | ||
49 | ||
50 | const updateFieldMatrixFromMouse = (field, matrix, gap = 50) => { | |
51 | const mousePosition = { | |
52 | x: 0, | |
53 | y: 0 | |
54 | }; | |
55 | ||
56 | fillMatrix(matrix, mousePosition.x, mousePosition.y, field.width, field.height, gap); | |
57 | ||
58 | const updateMousePosition = document.addEventListener('mousemove', (event) => { | |
97d30c50 BB |
59 | mousePosition.x = event.clientX; |
60 | mousePosition.y = event.clientY; | |
beaf2913 BB |
61 | fillMatrix(matrix, mousePosition.x, mousePosition.y, field.width, field.height, gap); |
62 | }); | |
63 | }; | |
64 | ||
65 | // Gets a matrix by calculating the offset between position and field. | |
66 | ||
67 | const fillMatrix = (matrix, x, y, w, h, gap = 50) => { | |
68 | for (let i = 0; i < w / gap; ++i) { | |
69 | matrix[i] = []; | |
70 | for (let j = 0; j < h / gap; ++j) { | |
71 | let targetX = i * gap; | |
72 | let targetY = j * gap; | |
73 | matrix[i][j] = calculateOffsetVector(x, y, targetX, targetY, w, h); | |
74 | } | |
75 | } | |
76 | }; | |
77 | ||
78 | // Calculates the offset vector of a point | |
79 | const calculateOffsetVector = (sourceX, sourceY, targetX, targetY, w, h) => { | |
80 | const xOffset = targetX - sourceX; | |
81 | const yOffset = targetY - sourceY; | |
82 | ||
97d30c50 BB |
83 | const calculatedMagnitude = Math.sqrt(Math.pow(xOffset, 2) + Math.pow(yOffset, 2)); |
84 | ||
beaf2913 BB |
85 | return { |
86 | position: { | |
87 | x: targetX, | |
88 | y: targetY | |
89 | }, | |
97d30c50 | 90 | color: getColor(calculatedMagnitude), |
beaf2913 | 91 | angle: Math.atan2(xOffset, yOffset), |
97d30c50 | 92 | magnitude: Math.min(calculatedMagnitude, kMaxVectorMagnitude) |
beaf2913 BB |
93 | }; |
94 | }; | |
95 | ||
96 | ||
97 | /* | |
98 | * Drawing | |
99 | */ | |
100 | ||
97d30c50 | 101 | // draw a vector |
beaf2913 | 102 | const drawVector = (context, i, j, vector) => { |
97d30c50 BB |
103 | const x = vector.position.x - vector.magnitude * Math.sin(vector.angle); |
104 | const y = vector.position.y - vector.magnitude * Math.cos(vector.angle); | |
105 | context.strokeStyle = vector.color; | |
beaf2913 BB |
106 | context.beginPath(); |
107 | context.moveTo(vector.position.x, vector.position.y); | |
108 | context.lineTo(Math.round(x), Math.round(y)); | |
109 | context.stroke(); | |
110 | }; | |
111 | ||
97d30c50 BB |
112 | // Gets the color depending on the calculated magnitude |
113 | // as well as the color scheme | |
114 | ||
115 | const getColor = (calculatedMagnitude) => { | |
116 | ||
117 | let colorValue = Math.round(calculatedMagnitude * 255 / (kMaxVectorMagnitude * kMagnitudeColorMultiplier)) % 255; | |
beaf2913 | 118 | |
beaf2913 | 119 | if (window.matchMedia('(prefers-color-scheme: dark)').matches) { |
97d30c50 | 120 | colorValue = 255 - colorValue % 255; |
beaf2913 BB |
121 | } |
122 | ||
97d30c50 BB |
123 | // Initialize an array with the value, converts it to a hex string and joins it. |
124 | return `#${Array(3).fill(colorValue.toString(16)).join('')}`; | |
beaf2913 BB |
125 | }; |
126 | ||
127 | ||
128 | // Draws field on canvas | |
129 | ||
130 | const drawFieldMatrixOnCanvas = (matrix, context) => { | |
131 | ||
97d30c50 | 132 | context.setLineDash([25, 10]) |
beaf2913 BB |
133 | context.clearRect(0, 0, context.canvas.width, context.canvas.height); |
134 | ||
135 | for (let i = 0; i < matrix.length; ++i) { | |
136 | const row = matrix[i]; | |
137 | for (let j = 0; j < row.length; ++j) { | |
138 | const vector = row[j] | |
139 | ||
140 | if (vector) { | |
141 | drawVector(context, i, j, vector); | |
142 | } | |
143 | } | |
144 | } | |
145 | }; | |
146 | ||
147 | /* | |
148 | * Entrypoint | |
149 | */ | |
150 | ||
151 | const run = () => { | |
152 | ||
153 | const field = { | |
154 | width: 0, | |
155 | height: 0 | |
156 | }; | |
157 | const canvas = document.getElementById(kEntrypoint); | |
158 | ||
159 | updateFieldFromCanvas(field, canvas); | |
160 | ||
161 | const matrix = [[]]; | |
162 | updateFieldMatrixFromMouse(field, matrix, kGap); | |
163 | ||
164 | const context = canvas.getContext('2d'); | |
165 | const drawFunction = () => { | |
166 | drawFieldMatrixOnCanvas(matrix, context); | |
167 | setTimeout(drawFunction, kDrawDelay); | |
168 | } | |
169 | ||
170 | setTimeout(drawFunction, 0); | |
171 | }; | |
172 | ||
173 | window.addEventListener('load', run); |