Skip to content

Commit bdc4592

Browse files
committed
change stepping spinner to animated time-map of events
1 parent d93254c commit bdc4592

File tree

6 files changed

+551
-54
lines changed

6 files changed

+551
-54
lines changed

src/api/canvas.ts

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
export const PI = Math.PI;
2+
export const PI2 = 2 * PI;
3+
export const PId2 = PI / 2;
4+
5+
/**
6+
* Round floating point with custom precision
7+
*/
8+
export function fround(n: number, precision?: number) {
9+
precision ??= 1e6;
10+
return Math.round(n * precision) / precision;
11+
}
12+
13+
/**
14+
* Degrees to radians
15+
*/
16+
export function deg2rad(deg: number) {
17+
return ((deg % 360) / 360) * PI2;
18+
}
19+
20+
/**
21+
* Radians to degrees
22+
*/
23+
export function rad2deg(rad: number) {
24+
return ((rad % PI2) / PI2) * 360;
25+
}
26+
27+
class Coordinate<T extends typeof Coordinate = typeof Coordinate> {
28+
x: number = 0;
29+
y: number = 0;
30+
31+
constructor(x: number, y: number) {
32+
this.set(x, y);
33+
}
34+
35+
set(x: number, y: number) {
36+
this.x = x;
37+
this.y = y;
38+
return this;
39+
}
40+
41+
toString() {
42+
return `{${this.x}, ${this.y}}`;
43+
}
44+
45+
shiftX(x: number) {
46+
this.x += x;
47+
return this;
48+
}
49+
50+
shiftY(y: number) {
51+
this.y += y;
52+
return this;
53+
}
54+
55+
shiftXY(x: number, y: number) {
56+
this.x += x;
57+
this.y += y;
58+
return this;
59+
}
60+
61+
equalXY(x: number, y: number) {
62+
return (x === this.x && y === this.y);
63+
}
64+
65+
equal(c: Coordinate) {
66+
return this.equalXY(c.x, c.y);
67+
}
68+
69+
round(precision?: number) {
70+
return this.set(
71+
fround(this.x, precision),
72+
fround(this.y, precision),
73+
);
74+
}
75+
}
76+
77+
/**
78+
* Point {x,y}
79+
*/
80+
export class Point extends Coordinate {
81+
constructor(x: number, y: number) {
82+
super(x, y);
83+
}
84+
85+
clone() {
86+
return new Point(this.x, this.y);
87+
}
88+
89+
/**
90+
* Get distance between two points
91+
*/
92+
proximity(p: Point) {
93+
return this.toVector(p).length();
94+
}
95+
96+
/**
97+
* Convert point to vector
98+
* Assuming this(x,y) is a v(0,0)
99+
* @note this(0,0) is at top left corner
100+
*/
101+
toVector(to: Point) {
102+
return new Vector(to.x - this.x, this.y - to.y);
103+
}
104+
105+
/**
106+
* Rotate point over center `axis` point by an angle
107+
* @note: positive angle value means counterclockwise
108+
*/
109+
rotate(angle: number, axis: Point) {
110+
return axis.toVector(this).rotate(angle).toPoint(axis);
111+
}
112+
}
113+
114+
/**
115+
* Vector with virtual base of {0,0}
116+
* and assumed position at top-left corner
117+
* that points to {this.x, this.y}
118+
*/
119+
export class Vector extends Coordinate {
120+
constructor(x: number, y: number) {
121+
super(x, y);
122+
}
123+
124+
clone() {
125+
return new Vector(this.x, this.y);
126+
}
127+
128+
/**
129+
* Convert vector to point
130+
* Assuming pBase(x,y) refers to v(0,0)
131+
* @note: base(0,0) is at top left corner
132+
*/
133+
toPoint(base: Point) {
134+
return new Point(base.x + this.x, base.y - this.y);
135+
}
136+
137+
/**
138+
* Create vector rotated on specific angle
139+
* produced vector may contain float epsilon errors
140+
* @note: Positive angle means counterclockwise
141+
*/
142+
rotate(angle: number) {
143+
const cos = Math.cos(angle);
144+
const sin = Math.sin(angle);
145+
return new Vector(
146+
this.x * cos - this.y * sin,
147+
this.x * sin + this.y * cos,
148+
);
149+
}
150+
151+
/**
152+
* Create vector rotated to left
153+
*/
154+
rotateLeft() {
155+
return new Vector(-this.y, this.x);
156+
}
157+
158+
/**
159+
* Create vector rotated to right
160+
*/
161+
rotateRight() {
162+
return new Vector(this.y, -this.x);
163+
}
164+
165+
/**
166+
* Create vector rotated backwards
167+
*/
168+
rotateBack() {
169+
return new Vector(-this.x, -this.y);
170+
}
171+
172+
/**
173+
* Return new vector mirrored over another vector interpreted as rotation axis
174+
*/
175+
mirror(axis: Vector) {
176+
const delta = this.xAxisAngle() - axis.xAxisAngle();
177+
const k = delta >= 0 ? -2 : 2;
178+
return this.rotate(k * this.angle(axis));
179+
}
180+
181+
length() {
182+
return Math.sqrt(this.dot(this));
183+
}
184+
185+
/**
186+
* Return new vector with new vector length
187+
*/
188+
setLength(newLength: number) {
189+
return (new Vector(newLength, 0)).rotate(this.xAxisAngle());
190+
}
191+
192+
/**
193+
* Return new vector halved in length
194+
*/
195+
half() {
196+
return new Vector(this.x / 2, this.y / 2);
197+
}
198+
199+
/**
200+
* Vector normalization
201+
*/
202+
normalize() {
203+
const length = this.length();
204+
return new Vector(this.x / length, this.y / length);
205+
}
206+
207+
/**
208+
* Dot product of two vectors
209+
*/
210+
dot(v: Vector) {
211+
return (this.x * v.x + this.y * v.y);
212+
}
213+
214+
angle(v: Vector) {
215+
return Math.acos(this.normalize().dot(v.normalize()));
216+
}
217+
218+
angleWithNorth() {
219+
return this.angle(new Vector(0, 1));
220+
}
221+
222+
angleWithEast() {
223+
return this.angle(new Vector(1, 0));
224+
}
225+
226+
angleWithSought() {
227+
return this.angle(new Vector(0, -1));
228+
}
229+
230+
angleWithWest() {
231+
return this.angle(new Vector(-1, 0));
232+
}
233+
234+
/**
235+
* Get angle of vector relative to OX in range [0 ... α ... XY.PI2]
236+
* @note produced angle will be always positive radian
237+
*/
238+
xAxisAngle() {
239+
let angle = Math.atan2(this.y, this.x);
240+
if (angle < 0) {
241+
angle += PI2;
242+
}
243+
return angle;
244+
}
245+
}
246+
247+
export class Box {
248+
w: number = 0;
249+
h: number = 0;
250+
// top-left
251+
tl: Point = new Point(0, 0);
252+
// top-right
253+
tr: Point = new Point(0, 0);
254+
// bottom-right
255+
br: Point = new Point(0, 0);
256+
// bottom-left
257+
bl: Point = new Point(0, 0);
258+
// center of a box
259+
c: Point = new Point(0, 0);
260+
261+
constructor(tl: Point, w: number, h: number) {
262+
this.tl.set(tl.x, tl.y);
263+
this.resize(w, h);
264+
}
265+
266+
clone() {
267+
return new Box(this.tl, this.w, this.h);
268+
}
269+
270+
resize(w: number, h: number) {
271+
this.w = w;
272+
this.h = h;
273+
this.tr = new Point(this.tl.x + w, this.tl.y);
274+
this.br = new Point(this.tl.x + w, this.tl.y + h);
275+
this.bl = new Point(this.tl.x, this.tl.y + h);
276+
this.c = new Point(this.tl.x + this.w / 2, this.tl.y + h / 2);
277+
278+
return this;
279+
}
280+
281+
toString() {
282+
return JSON.stringify({
283+
tl: this.tl.toString(),
284+
w: this.w,
285+
h: this.h,
286+
});
287+
}
288+
289+
contains(p: Point) {
290+
return (
291+
this.tl.x <= p.x && p.x <= this.tr.x &&
292+
this.tl.y <= p.y && p.y < this.bl.y
293+
);
294+
}
295+
}

src/state/app.state.svelte.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/view/menu/UpdatePace.svelte

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,41 @@
11
<script lang="ts">
2-
import { Fps } from '../../api/time.ts';
3-
import { useConfigState } from '../../state/config.state.svelte.ts';
42
import { useTelemetryState } from '../../state/telemetry.state.svelte.ts';
5-
import UpdatePaceAnime from './UpdatePaceAnime.svelte';
3+
import { startAnimation, update } from './UpdatePaceTimeMap.ts';
4+
import { onMount } from 'svelte';
65
7-
let upsValue = $state.raw(0);
8-
let animeEl: UpdatePaceAnime | null = null;
9-
const ups = new Fps((value) => (upsValue = value)).start();
10-
const config = useConfigState();
6+
let canvasEl: HTMLCanvasElement | null = null;
7+
let ctx: CanvasRenderingContext2D | null = null;
118
const ts = useTelemetryState();
129
10+
onMount(() => {
11+
ctx = canvasEl && canvasEl.getContext('2d');
12+
return ctx && startAnimation(ctx);
13+
});
14+
1315
ts.timeOfCollection.subscribe(() => {
14-
ups.tick();
15-
animeEl?.tick();
16+
update();
1617
});
1718
</script>
1819

1920
<div
20-
class="spinner"
21-
title="Pace of update"
22-
class:tc-attention={config.paused}
21+
class="time-map"
22+
title="Time map"
2323
>
24-
<div>{upsValue} u/s</div>
25-
<UpdatePaceAnime bind:this={animeEl} />
24+
<canvas bind:this={canvasEl}></canvas>
2625
</div>
2726

2827
<style lang="scss">
29-
.spinner {
28+
.time-map {
3029
display: flex;
3130
align-items: center;
3231
gap: 0.4rem;
3332
line-height: 1;
33+
34+
canvas {
35+
border-radius: 50%;
36+
// downscaled
37+
width: 1.5rem;
38+
height: 1.5rem;
39+
}
3440
}
3541
</style>

src/view/menu/UpdatePaceAnime.svelte

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)