move: implement circle movement
This commit is contained in:
@@ -4,8 +4,11 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Hello typescript</title>
|
<title>Hello typescript</title>
|
||||||
|
<style>
|
||||||
|
canvas { display: block; width: 100vw; height: 100vh; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body style="margin: 0; padding 0; overflow: hidden; background-color: #000000">
|
<body style="margin: 0; padding: 0; overflow: hidden; background-color: #000000">
|
||||||
<canvas id="canvas" tabindex="0"></canvas>
|
<canvas id="canvas" tabindex="0"></canvas>
|
||||||
<script type="module" src="./move.js"></script>
|
<script type="module" src="./move.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import { drawCircle, resizeCanvas } from "./common.js";
|
|||||||
const SEGMENT_SPACING = 100;
|
const SEGMENT_SPACING = 100;
|
||||||
|
|
||||||
function initTrail(pos: Vec2d, len: number) {
|
function initTrail(pos: Vec2d, len: number) {
|
||||||
return Array.from({ length: len }, (_, i) => pos.add(new Vec2d(0, -1).scale(i * SEGMENT_SPACING)));
|
return Array.from({ length: len }, (_, i) =>
|
||||||
|
pos.add(new Vec2d(0, -1).scale(i * SEGMENT_SPACING)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// state
|
// state
|
||||||
@@ -12,19 +14,35 @@ let target: Vec2d | undefined = undefined;
|
|||||||
let trail = initTrail(new Vec2d(200, 200), 5);
|
let trail = initTrail(new Vec2d(200, 200), 5);
|
||||||
let velocity = new Vec2d(500, 500);
|
let velocity = new Vec2d(500, 500);
|
||||||
let pause = false;
|
let pause = false;
|
||||||
let mode: "follow" | "bounce" = "bounce";
|
let mode: "follow" | "bounce" | "circle" = "follow";
|
||||||
let lastFrameTime: number | undefined = undefined;
|
let lastFrameTime: number | undefined = undefined;
|
||||||
|
|
||||||
function update(ctx: CanvasRenderingContext2D, timestamp: number) {
|
function update(ctx: CanvasRenderingContext2D, timestamp: number) {
|
||||||
if (!lastFrameTime) lastFrameTime = timestamp;
|
if (!lastFrameTime) lastFrameTime = timestamp;
|
||||||
const dt = (timestamp - lastFrameTime) / 1000; // seconds
|
const dt = (timestamp - lastFrameTime) / 1000; // seconds
|
||||||
lastFrameTime = timestamp;
|
lastFrameTime = timestamp;
|
||||||
|
if (!pause) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "bounce": updateBounce(ctx, dt); break;
|
case "bounce":
|
||||||
case "follow": updateFollow(ctx, dt); break;
|
updateBounce(ctx, dt);
|
||||||
default: throw new Error(`Unknown mode: ${mode}`);
|
break;
|
||||||
|
case "follow":
|
||||||
|
updateFollow(ctx, dt);
|
||||||
|
break;
|
||||||
|
case "circle":
|
||||||
|
updateCircle(ctx, dt);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown mode: ${mode}`);
|
||||||
}
|
}
|
||||||
if (!pause) window.requestAnimationFrame(t => update(ctx, t));
|
}
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||||
|
for (const pos of trail) {
|
||||||
|
drawCircle(ctx, pos, 20, 0xff00ff);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.requestAnimationFrame((t) => update(ctx, t));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFollow(ctx: CanvasRenderingContext2D, dt: number) {
|
function updateFollow(ctx: CanvasRenderingContext2D, dt: number) {
|
||||||
@@ -34,32 +52,43 @@ function updateFollow(ctx: CanvasRenderingContext2D, dt: number) {
|
|||||||
// frame, so this is framerate dependent:
|
// frame, so this is framerate dependent:
|
||||||
// trail[0] = trail[0].lerp(target, 0.1);
|
// trail[0] = trail[0].lerp(target, 0.1);
|
||||||
// use exponential decay instead, this is framerate independent:
|
// use exponential decay instead, this is framerate independent:
|
||||||
const alpha = Math.exp(-10 * dt)
|
const alpha = Math.exp(-10 * dt);
|
||||||
trail[0] = trail[0].lerp(target, alpha);
|
trail[0] = trail[0].lerp(target, alpha);
|
||||||
updateTrail();
|
updateTrail();
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
||||||
for (const pos of trail) {
|
|
||||||
drawCircle(ctx, pos, 20, 0xFF00FF);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateBounce(ctx: CanvasRenderingContext2D, dt: number) {
|
function updateBounce(ctx: CanvasRenderingContext2D, dt: number) {
|
||||||
// update head pos
|
// update head pos
|
||||||
// P_t+1 = P_t + V * t
|
// P_t+1 = P_t + V * t
|
||||||
const newPos = trail[0].add(velocity.scale(dt));
|
const newPos = trail[0].add(velocity.scale(dt));
|
||||||
if (newPos.x > ctx.canvas.width - 100) { velocity.x *= -1; newPos.x = ctx.canvas.width - 100; }
|
if (newPos.x > ctx.canvas.width - 100) {
|
||||||
if (newPos.y > ctx.canvas.height - 100) { velocity.y *= -1; newPos.y = ctx.canvas.height - 100; }
|
velocity.x *= -1;
|
||||||
if (newPos.x < 100) { velocity.x *= -1; newPos.x = 100; }
|
newPos.x = ctx.canvas.width - 100;
|
||||||
if (newPos.y < 100) { velocity.y *= -1; newPos.y = 100; }
|
}
|
||||||
trail[0] = newPos;
|
if (newPos.y > ctx.canvas.height - 100) {
|
||||||
updateTrail();
|
velocity.y *= -1;
|
||||||
|
newPos.y = ctx.canvas.height - 100;
|
||||||
|
}
|
||||||
|
if (newPos.x < 100) {
|
||||||
|
velocity.x *= -1;
|
||||||
|
newPos.x = 100;
|
||||||
|
}
|
||||||
|
if (newPos.y < 100) {
|
||||||
|
velocity.y *= -1;
|
||||||
|
newPos.y = 100;
|
||||||
|
}
|
||||||
|
trail[0] = newPos;
|
||||||
|
updateTrail();
|
||||||
|
}
|
||||||
|
|
||||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
function updateCircle(ctx: CanvasRenderingContext2D, dt: number) {
|
||||||
for (const pos of trail) {
|
const center = new Vec2d(ctx.canvas.width / 2, ctx.canvas.height / 2);
|
||||||
drawCircle(ctx, pos, 20, 0xFF00FF);
|
const rel = trail[0].sub(center);
|
||||||
}
|
const radius = rel.length();
|
||||||
|
const theta = Math.atan2(rel.y, rel.x) + dt;
|
||||||
|
trail[0] = center.add(Vec2d.fromPolar(radius, theta));
|
||||||
|
updateTrail();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTrail() {
|
function updateTrail() {
|
||||||
@@ -78,33 +107,32 @@ function updateTrail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
const canvas = document.getElementById("canvas") as HTMLCanvasElement | null;
|
const canvas = document.getElementById("canvas") as HTMLCanvasElement | null;
|
||||||
if (!canvas) throw new Error("unable to get canvas HTML element");
|
if (!canvas) throw new Error("unable to get canvas HTML element");
|
||||||
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D | null;
|
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D | null;
|
||||||
if (!ctx) throw new Error("unable to get canvas 2D context");
|
if (!ctx) throw new Error("unable to get canvas 2D context");
|
||||||
ctx.canvas.width = window.innerWidth
|
ctx.canvas.width = ctx.canvas.clientWidth;
|
||||||
ctx.canvas.height = window.innerHeight
|
ctx.canvas.height = ctx.canvas.clientHeight;
|
||||||
|
|
||||||
canvas.onmousemove = (evt) => {
|
canvas.onmousemove = (evt) => {
|
||||||
const {clientX, clientY} = evt;
|
const { clientX, clientY } = evt;
|
||||||
target = new Vec2d(clientX, clientY);
|
target = new Vec2d(clientX, clientY);
|
||||||
}
|
};
|
||||||
|
|
||||||
canvas.onclick = () => {
|
canvas.onclick = () => {
|
||||||
if (pause) {
|
pause = !pause;
|
||||||
window.requestAnimationFrame(t => update(ctx, t));
|
};
|
||||||
}
|
|
||||||
pause = !pause;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onkeydown = (evt) => {
|
window.onkeydown = (evt) => {
|
||||||
if (mode === "follow") mode = "bounce";
|
if (mode === "bounce") mode = "follow";
|
||||||
else mode = "follow"
|
else if (mode === "follow") mode = "circle";
|
||||||
}
|
else if (mode === "circle") mode = "bounce";
|
||||||
|
console.log(mode);
|
||||||
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', () => resizeCanvas(ctx));
|
window.addEventListener("resize", () => resizeCanvas(ctx));
|
||||||
|
|
||||||
window.requestAnimationFrame(t => update(ctx, t));
|
window.requestAnimationFrame((t) => update(ctx, t));
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ export default class Vec2d implements Point {
|
|||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromPolar(r: number, theta: number) {
|
||||||
|
return new Vec2d(r * Math.cos(theta), r * Math.sin(theta));
|
||||||
|
}
|
||||||
|
|
||||||
static random(maxX: number, maxY: number) {
|
static random(maxX: number, maxY: number) {
|
||||||
return new Vec2d(Math.random() * maxX, Math.random() * maxY);
|
return new Vec2d(Math.random() * maxX, Math.random() * maxY);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user