]>
Commit | Line | Data |
---|---|---|
1 | // App Config | |
2 | ||
3 | const kEntrypoint = "breadsticks"; | |
4 | const kGap = 50; | |
5 | const kDrawDelay = 100; | |
6 | const kDarkDrawingColor = '#fff'; | |
7 | const kLightDrawingColor = '#000'; | |
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 | |
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) => { | |
59 | mousePosition.x = event.clientX; | |
60 | mousePosition.y = event.clientY; | |
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 | ||
83 | const calculatedMagnitude = Math.sqrt(Math.pow(xOffset, 2) + Math.pow(yOffset, 2)); | |
84 | ||
85 | return { | |
86 | position: { | |
87 | x: targetX, | |
88 | y: targetY | |
89 | }, | |
90 | color: getColor(calculatedMagnitude), | |
91 | angle: Math.atan2(xOffset, yOffset), | |
92 | magnitude: Math.min(calculatedMagnitude, kMaxVectorMagnitude) | |
93 | }; | |
94 | }; | |
95 | ||
96 | ||
97 | /* | |
98 | * Drawing | |
99 | */ | |
100 | ||
101 | // draw a vector | |
102 | const drawVector = (context, i, j, vector) => { | |
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; | |
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 | ||
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; | |
118 | ||
119 | if (window.matchMedia('(prefers-color-scheme: dark)').matches) { | |
120 | colorValue = 255 - colorValue % 255; | |
121 | } | |
122 | ||
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('')}`; | |
125 | }; | |
126 | ||
127 | ||
128 | // Draws field on canvas | |
129 | ||
130 | const drawFieldMatrixOnCanvas = (matrix, context) => { | |
131 | ||
132 | context.setLineDash([25, 10]) | |
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); |