Skip to content

Commit e588bc8

Browse files
author
Bryan Parker
committed
Adds the ability to move to a target along a path
1 parent 7c4f668 commit e588bc8

File tree

5 files changed

+128
-11
lines changed

5 files changed

+128
-11
lines changed

src/animations/animations.ts

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,32 +148,88 @@ export function isTargetAnimation<T>(o: any): o is TargetAnimation<T> {
148148
}
149149

150150

151-
type MoveToTargetArgs = { target: Shape, destination: Shape | Point } & AnimationArgs;
151+
type MoveToTargetArgs = { target: Shape, destination: Shape | Point, alongPath?: Point[] | PointsAware } & AnimationArgs;
152152

153153
export class MoveToTarget extends Animation implements TargetAnimation<Shape> {
154154
private _target: Shape;
155155
private startLocation: Point
156156
private endLocation: Point
157157

158-
constructor({ target, destination, ...baseConfig }: MoveToTargetArgs) {
158+
private pathObj?: Point[] | PointsAware;
159+
private path?: Point[];
160+
161+
constructor({ target, destination, alongPath, ...baseConfig }: MoveToTargetArgs) {
159162
super(baseConfig);
160163

161164
this._target = target;
162165
this.startLocation = this._target.center();
163166
this.endLocation = isShape(destination) ? destination.center() : destination;
167+
this.pathObj = alongPath;
164168
}
165169

166170
update(pctComplete: number, reversing: boolean): Animatable[] {
167-
const [start, end] = reversing ? [this.endLocation, this.startLocation] : [this.startLocation, this.endLocation];
171+
// const [start, end] = reversing ? [this.endLocation, this.startLocation] : [this.startLocation, this.endLocation];
168172

169-
const [nX, nY] = this.updateWithEase(start, end, pctComplete);
173+
// const [nX, nY] = this.updateWithEase(start, end, pctComplete);
174+
const [nX, nY] = this.getNewPoint(pctComplete, reversing);
170175
this._target.moveCenter([nX, nY]);
171176

172177
return [this._target];
173178
}
174179

180+
private getNewPoint(pctComplete: number, reversing: boolean): Point {
181+
if (this.path && this.path.length > 0) {
182+
const [from, to] = reversing ? [this.path.length, 0] : [0, this.path.length];
183+
184+
const idxWithEase = this.updateWithEase(from, to, pctComplete);
185+
const idx = Math.floor(idxWithEase);
186+
187+
if (idx < this.path.length - 1) {
188+
const [pt, nextPt] = [this.path[idx], this.path[idx + 1]];
189+
190+
return arrLerp(pt, nextPt, idxWithEase - idx) as Point;
191+
} else {
192+
return this.path[Math.min(idx, this.path.length - 1)];
193+
}
194+
} else {
195+
const [start, end] = reversing ? [this.endLocation, this.startLocation] : [this.startLocation, this.endLocation];
196+
return this.updateWithEase(start, end, pctComplete) as Point;
197+
}
198+
}
199+
175200
protected initState(): void {
176201
this.startLocation = this._target.center();
202+
203+
if (this.pathObj) {
204+
const pathPoints = Array.isArray(this.pathObj) ? this.pathObj : this.pathObj.points();
205+
206+
// Find the closest point along the path to the start and end locations
207+
let [minStartDist, pathStart] = [Number.MAX_VALUE, -1];
208+
let [minEndDist, pathEnd] = [Number.MAX_VALUE, -1];
209+
210+
for (let i = 0; i < pathPoints.length; i++) {
211+
const pt = pathPoints[i];
212+
const ds = distance(pt, this.startLocation);
213+
const dt = distance(pt, this.endLocation);
214+
215+
if (ds < minStartDist) {
216+
minStartDist = ds;
217+
pathStart = i;
218+
}
219+
220+
if (dt < minEndDist) {
221+
minEndDist = dt;
222+
pathEnd = i;
223+
}
224+
}
225+
226+
let segment = pathPoints.slice(Math.min(pathStart, pathEnd), Math.max(pathStart, pathEnd));
227+
if (pathStart > pathEnd) {
228+
segment = segment.toReversed();
229+
}
230+
231+
this.path = segment;
232+
}
177233
}
178234

179235
resetState(): void {

src/shapes/line_through_points.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import * as math from '@/math';
55

66

77
export class LineThroughPoints extends Line {
8+
private readonly _length: number;
9+
810
constructor({ p1, p2, length = 3, ...styleArgs }: { p1: Point; p2: Point; length: number; } & Prettify<StyleArgs>) {
911
const [from, to] = LineThroughPoints.linePoints(p1, p2, length);
1012
super({ from, to, ...styleArgs });
13+
this._length = length;
1114
}
1215

1316
private static linePoints(p1: Point, p2: Point, length: number): [Point, Point] {
@@ -26,4 +29,8 @@ export class LineThroughPoints extends Line {
2629

2730
return [e1, e2] as [Point, Point];
2831
}
32+
33+
changePoints(p1: Point, p2: Point) {
34+
this.setPoints(LineThroughPoints.linePoints(p1, p2, this._length));
35+
}
2936
}

src/shapes/primitives/point_shape.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ export class PointShape implements Shape, Styleable, PointsAware {
167167
return this._points;
168168
}
169169

170+
public setPoints(points: Point[]) {
171+
this._points = points;
172+
}
173+
170174
computedPoints(): Point[] {
171175
const [cX, cY] = this.center();
172176
const computedPoints = [];

test-cases/tests.js

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -873,17 +873,67 @@ export class VisualTests extends TestSuite {
873873
// new TestScene({ canvas: new HtmlCanvas(canvas), staticScene: true }).compose().render();
874874
// }
875875

876-
testCanvasWidth(canvas, done) {
876+
// testCanvasWidth(canvas, done) {
877+
// class TestScene extends Scene {
878+
// compose() {
879+
// // const square = new Square();
880+
// // this.add(square);
881+
// this.add(new Axes());
882+
883+
// return this;
884+
// }
885+
// }
886+
887+
// new TestScene({ canvas: new HtmlCanvas(canvas), staticScene: true }).compose().render();
888+
// }
889+
890+
testMoveAlongPathToDestination(canvas, done) {
877891
class TestScene extends Scene {
878892
compose() {
879-
// const square = new Square();
880-
// this.add(square);
881-
this.add(new Axes());
893+
const ax = new Axes({
894+
xRange: [0, 6],
895+
yRange: [0, 16],
896+
xLength: 10,
897+
yLength: 6,
898+
yAxisTickStep: 2,
899+
});
900+
901+
const plot = ax.plot(x => x * x);
902+
const p = ax.relativePoint([2, 4]);
903+
const q = ax.relativePoint([3, 9])
904+
905+
const pd = new Dot({ pt: p });
906+
const qd = new Dot({ pt: q });
907+
const t = new TangentLine({ shape: plot, x: p[0], length: 5, lineColor: Colors.pink() });
908+
const s = new LineThroughPoints({ p1: p, p2: q, length: 5, lineColor: Colors.blue() });
909+
910+
this.add(
911+
ax,
912+
plot,
913+
t,
914+
s,
915+
pd,
916+
qd,
917+
);
918+
919+
// this.add(new MoveAlongPath({ target: qd, path: plot, duration: 2000 }));
920+
class Updater extends Animation {
921+
update(pctComplete, reversing) {
922+
s.changePoints(p, qd.center());
923+
924+
return [];
925+
}
926+
}
927+
928+
this.add(
929+
new MoveToTarget({ target: qd, destination: pd, alongPath: plot, duration: 2000, easing: Easing.linear, repeat: true, }),
930+
new Updater({ duration: 2000, easing: Easing.linear, repeat: true, }),
931+
);
882932

883933
return this;
884934
}
885935
}
886936

887-
new TestScene({ canvas: new HtmlCanvas(canvas), staticScene: true }).compose().render();
937+
new TestScene({ canvas: new HtmlCanvas(canvas), staticScene: false, }).compose().render();
888938
}
889939
}

tsconfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"compilerOptions": {
3-
"target": "ES2020",
3+
"target": "ES2022",
44
"useDefineForClassFields": true,
55
"module": "ESNext",
66
"baseUrl": ".",
7-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
7+
"lib": ["ES2022", "ESNext.Array", "DOM", "DOM.Iterable"],
88
"skipLibCheck": true,
99

1010
"moduleResolution": "bundler",

0 commit comments

Comments
 (0)