Skip to content

Commit 59fcf11

Browse files
committed
fix vector math
1 parent 8f561fc commit 59fcf11

File tree

4 files changed

+221
-176
lines changed

4 files changed

+221
-176
lines changed

src/api/canvas.ts

+58-88
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/**
2+
* Module assumption:
3+
* - Coordinate system of canvas html element
4+
*/
5+
16
export const PI = Math.PI;
27
export const PI2 = 2 * PI;
38
export const PId2 = PI / 2;
@@ -24,7 +29,7 @@ export function rad2deg(rad: number) {
2429
return ((rad % PI2) / PI2) * 360;
2530
}
2631

27-
class Coordinate<T extends typeof Coordinate = typeof Coordinate> {
32+
class XY {
2833
x: number = 0;
2934
y: number = 0;
3035

@@ -38,6 +43,10 @@ class Coordinate<T extends typeof Coordinate = typeof Coordinate> {
3843
return this;
3944
}
4045

46+
clone() {
47+
return new XY(this.x, this.y);
48+
}
49+
4150
toString() {
4251
return `{${this.x}, ${this.y}}`;
4352
}
@@ -58,12 +67,12 @@ class Coordinate<T extends typeof Coordinate = typeof Coordinate> {
5867
return this;
5968
}
6069

61-
equalXY(x: number, y: number) {
70+
hasSameXY(x: number, y: number) {
6271
return (x === this.x && y === this.y);
6372
}
6473

65-
equal(c: Coordinate) {
66-
return this.equalXY(c.x, c.y);
74+
isEqualTo(xy: XY) {
75+
return this.hasSameXY(xy.x, xy.y);
6776
}
6877

6978
round(precision?: number) {
@@ -74,10 +83,7 @@ class Coordinate<T extends typeof Coordinate = typeof Coordinate> {
7483
}
7584
}
7685

77-
/**
78-
* Point {x,y}
79-
*/
80-
export class Point extends Coordinate {
86+
export class Point extends XY {
8187
constructor(x: number, y: number) {
8288
super(x, y);
8389
}
@@ -90,33 +96,32 @@ export class Point extends Coordinate {
9096
* Get distance between two points
9197
*/
9298
proximity(p: Point) {
93-
return this.toVector(p).length();
99+
return this.vectorTo(p).length;
94100
}
95101

96102
/**
97103
* Convert point to vector
98104
* Assuming this(x,y) is a v(0,0)
99105
* @note this(0,0) is at top left corner
100106
*/
101-
toVector(to: Point) {
102-
return new Vector(to.x - this.x, this.y - to.y);
107+
vectorTo(to: XY) {
108+
return new Vector(to.x - this.x, to.y - this.y);
103109
}
104110

105111
/**
106112
* Rotate point over center `axis` point by an angle
107-
* @note: positive angle value means counterclockwise
113+
* @note: positive `radAngle` means counterclockwise
108114
*/
109-
rotate(angle: number, axis: Point) {
110-
return axis.toVector(this).rotate(angle).toPoint(axis);
115+
rotate(radAngle: number, axis: Point) {
116+
const p = axis.vectorTo(this).rotate(radAngle).atBase(axis);
117+
return this.set(p.x, p.y);
111118
}
112119
}
113120

114121
/**
115-
* Vector with virtual base of {0,0}
116-
* and assumed position at top-left corner
117-
* that points to {this.x, this.y}
122+
* Vector with virtual base of {0,0} that points to {x,y}
118123
*/
119-
export class Vector extends Coordinate {
124+
export class Vector extends XY {
120125
constructor(x: number, y: number) {
121126
super(x, y);
122127
}
@@ -126,121 +131,85 @@ export class Vector extends Coordinate {
126131
}
127132

128133
/**
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
134+
* Get point where vector points from `base` point of view
135+
* Assuming base(x,y) refers to v(0,0) of `this` vector
132136
*/
133-
toPoint(base: Point) {
134-
return new Point(base.x + this.x, base.y - this.y);
137+
atBase(base: Point) {
138+
return new Point(base.x + this.x, base.y + this.y);
135139
}
136140

137141
/**
138-
* Create vector rotated on specific angle
142+
* Rotate at specific angle
139143
* produced vector may contain float epsilon errors
140-
* @note: Positive angle means counterclockwise
144+
* @note: positive `radAngle` means counterclockwise
141145
*/
142-
rotate(angle: number) {
143-
const cos = Math.cos(angle);
144-
const sin = Math.sin(angle);
145-
return new Vector(
146+
rotate(radAngle: number) {
147+
const cos = Math.cos(-radAngle);
148+
const sin = Math.sin(-radAngle);
149+
150+
return this.set(
146151
this.x * cos - this.y * sin,
147152
this.x * sin + this.y * cos,
148153
);
149154
}
150155

151-
/**
152-
* Create vector rotated to left
153-
*/
154156
rotateLeft() {
155-
return new Vector(-this.y, this.x);
157+
return this.set(-this.y, this.x);
156158
}
157159

158-
/**
159-
* Create vector rotated to right
160-
*/
161160
rotateRight() {
162-
return new Vector(this.y, -this.x);
161+
return this.set(this.y, -this.x);
163162
}
164163

165-
/**
166-
* Create vector rotated backwards
167-
*/
168164
rotateBack() {
169-
return new Vector(-this.x, -this.y);
165+
return this.set(-this.x, -this.y);
170166
}
171167

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();
168+
mirrorOver(axis: Vector) {
169+
const delta = this.angleWithX - axis.angleWithX;
177170
const k = delta >= 0 ? -2 : 2;
178171
return this.rotate(k * this.angle(axis));
179172
}
180173

181-
length() {
174+
get length() {
182175
return Math.sqrt(this.dot(this));
183176
}
184177

185-
/**
186-
* Return new vector with new vector length
187-
*/
188178
setLength(newLength: number) {
189-
return (new Vector(newLength, 0)).rotate(this.xAxisAngle());
179+
const v = new Vector(newLength, 0).rotate(this.angleWithX);
180+
return this.set(v.x, v.y);
190181
}
191182

192-
/**
193-
* Return new vector halved in length
194-
*/
195183
half() {
196-
return new Vector(this.x / 2, this.y / 2);
184+
return this.set(this.x / 2, this.y / 2);
197185
}
198186

199187
/**
200-
* Vector normalization
188+
* Get angle of vector relative to X axis in range [0 ... α ... PI2] counterclockwise
201189
*/
202-
normalize() {
203-
const length = this.length();
204-
return new Vector(this.x / length, this.y / length);
205-
}
190+
get angleWithX() {
191+
const angle = Math.atan2(-this.y, this.x);
206192

207-
/**
208-
* Dot product of two vectors
209-
*/
210-
dot(v: Vector) {
211-
return (this.x * v.x + this.y * v.y);
193+
return (angle < 0) ? angle + PI2 : angle;
212194
}
213195

214196
angle(v: Vector) {
215197
return Math.acos(this.normalize().dot(v.normalize()));
216198
}
217199

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));
200+
/**
201+
* Return normalized vector
202+
*/
203+
normalize() {
204+
const length = this.length;
205+
return new Vector(this.x / length, this.y / length);
232206
}
233207

234208
/**
235-
* Get angle of vector relative to OX in range [0 ... α ... XY.PI2]
236-
* @note produced angle will be always positive radian
209+
* Dot product of two vectors
237210
*/
238-
xAxisAngle() {
239-
let angle = Math.atan2(this.y, this.x);
240-
if (angle < 0) {
241-
angle += PI2;
242-
}
243-
return angle;
211+
dot(v: Vector) {
212+
return (this.x * v.x + this.y * v.y);
244213
}
245214
}
246215

@@ -280,9 +249,10 @@ export class Box {
280249

281250
toString() {
282251
return JSON.stringify({
283-
tl: this.tl.toString(),
284252
w: this.w,
285253
h: this.h,
254+
tl: this.tl.toString(),
255+
c: this.c.toString(),
286256
});
287257
}
288258

src/view/menu/UpdatePace.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
return ctx && startAnimation(ctx);
1313
});
1414
15-
ts.timeOfCollection.subscribe(() => {
16-
update();
15+
ts.timeOfCollection.subscribe((v) => {
16+
update(v);
1717
});
1818
</script>
1919

src/view/menu/UpdatePaceTimeMap.ts

+34-28
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { deg2rad, PI2, Point, Vector } from '../../api/canvas.ts';
22

33
interface IMemo {
4-
when: number;
5-
vector: Vector;
4+
whenOccurred: number;
5+
whenAdded: number;
66
age: number;
7+
timePoint: Point;
8+
vector: Vector;
79
}
810

911
let raf = 0;
@@ -15,7 +17,8 @@ const SHADOW_WIDTH = 4;
1517
const pRotationAxis = new Point(R, R);
1618
const WHITE = 'rgb(100% 100% 100%)';
1719
const BLACK = 'rgb(0% 0% 0%)';
18-
const ANIMATION_DURATION = 2e3;
20+
const ANIMATION_DURATION = 1.5e3;
21+
const ANIMATION_DELTA_PX = R / ANIMATION_DURATION;
1922
let primaryColour: string = BLACK;
2023
let shadowColour: string = WHITE;
2124
const memory: IMemo[] = [];
@@ -32,15 +35,21 @@ export function startAnimation(ctx: CanvasRenderingContext2D) {
3235
};
3336
}
3437

35-
export function update() {
36-
const when = Date.now();
37-
const angle = -(when % 1000) * 360 / 1000;
38-
const vector = new Vector(R, 0).rotate(deg2rad(angle));
38+
export function update(timeOfCollection: number) {
39+
const whenAdded = Date.now();
40+
const angle = (timeOfCollection % 1000) * 360 / 1000;
41+
const vector = new Vector(R, 0)
42+
.rotate(deg2rad(-angle))
43+
// rotate left to adjust zero angle to point at {0,-1} (north)
44+
.rotateLeft();
45+
const timePoint = vector.atBase(pRotationAxis);
3946

4047
memory.unshift({
41-
when,
42-
vector,
48+
whenOccurred: timeOfCollection,
49+
whenAdded,
4350
age: 0,
51+
timePoint,
52+
vector: vector.rotateBack(),
4453
});
4554
}
4655

@@ -63,24 +72,21 @@ function initContext(_ctx: CanvasRenderingContext2D) {
6372
function draw() {
6473
const drawTime = Date.now();
6574
ctx.clearRect(0, 0, D, D);
75+
drawCenter();
6676

67-
if (memory.length) {
68-
for (let n = 0, N = memory.length; n < N; n++) {
69-
const memo = memory[n];
70-
memo.age = drawTime - memo.when;
71-
drawLine(memo);
72-
}
77+
for (let n = 0, N = memory.length; n < N; n++) {
78+
const memo = memory[n];
79+
memo.age = drawTime - memo.whenAdded;
80+
drawLine(memo);
81+
}
7382

74-
let n = memory.length;
75-
while (n--) {
76-
if (memory[n].age >= ANIMATION_DURATION) {
77-
memory.pop();
78-
} else {
79-
break;
80-
}
83+
let n = memory.length;
84+
while (n--) {
85+
if (memory[n].age >= ANIMATION_DURATION) {
86+
memory.pop();
87+
} else {
88+
break;
8189
}
82-
} else {
83-
drawCenter();
8490
}
8591

8692
raf = requestAnimationFrame(draw);
@@ -96,13 +102,13 @@ function drawCenter() {
96102
}
97103

98104
function drawLine(memo: IMemo) {
99-
const length = R - memo.age * R / ANIMATION_DURATION;
100-
const p = memo.vector.setLength(length).toPoint(pRotationAxis);
105+
const length = R - memo.age * ANIMATION_DELTA_PX;
106+
const p = memo.vector.setLength(length).atBase(memo.timePoint);
101107

102108
ctx.save();
103109
ctx.beginPath();
104-
ctx.moveTo(pRotationAxis.x, pRotationAxis.y);
105-
ctx.lineTo(p.x, p.y);
110+
ctx.moveTo(p.x, p.y);
111+
ctx.lineTo(memo.timePoint.x, memo.timePoint.y);
106112
ctx.stroke();
107113
ctx.closePath();
108114
ctx.restore();

0 commit comments

Comments
 (0)