snake: add catmull-rom rendering mode
This commit is contained in:
@@ -3,17 +3,6 @@ export interface Point {
|
|||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DIRECTIONS: Point[] = [
|
|
||||||
{ x: 0, y: -1}, // N
|
|
||||||
{ x: -1, y: -1}, // NW
|
|
||||||
{ x: 1, y: -1}, // NE
|
|
||||||
{ x: -1, y: 0}, // W
|
|
||||||
{ x: 1, y: 0}, // E
|
|
||||||
{ x: 0, y: 1}, // S
|
|
||||||
{ x: -1, y: 1}, // SW
|
|
||||||
{ x: 1, y: 1}, // SE
|
|
||||||
];
|
|
||||||
|
|
||||||
export function lerpPoint(a: Point, b: Point, p: number) {
|
export function lerpPoint(a: Point, b: Point, p: number) {
|
||||||
return {
|
return {
|
||||||
x: a.x + (b.x - a.x) * p,
|
x: a.x + (b.x - a.x) * p,
|
||||||
@@ -150,7 +139,7 @@ export function drawLine(ctx: CanvasRenderingContext2D, start: Point, end: Point
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
function rgbToString(color: number) {
|
export function rgbToString(color: number) {
|
||||||
return `#${color.toString(16).padStart(6, '0')}`;
|
return `#${color.toString(16).padStart(6, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Vec2d from "./vec.js";
|
import Vec2d from "./vec.js";
|
||||||
import { resizeCanvas, fillCircle, lerp, lerpRgb } from "./common.js";
|
import { resizeCanvas, fillCircle, lerp, lerpRgb, catmullRom, cubicBezier, rgbToString, drawCircle } from "./common.js";
|
||||||
|
|
||||||
// constants
|
// constants
|
||||||
const SNAKE_RADIUS = 10;
|
const SNAKE_RADIUS = 10;
|
||||||
@@ -31,6 +31,7 @@ let interpolation_factor = 10;
|
|||||||
let paused = false;
|
let paused = false;
|
||||||
let mouse_control = false;
|
let mouse_control = false;
|
||||||
let status_line_timeout_id: number | undefined = undefined;
|
let status_line_timeout_id: number | undefined = undefined;
|
||||||
|
let draw_mode: "linear" | "catmull" = "linear";
|
||||||
|
|
||||||
let lastTime: number | undefined = undefined;
|
let lastTime: number | undefined = undefined;
|
||||||
|
|
||||||
@@ -49,15 +50,30 @@ function drawSnake(ctx: CanvasRenderingContext2D, bounds: Vec2d) {
|
|||||||
fillCircle(ctx, tail, SNAKE_RADIUS / 2, TAIL_COLOR)
|
fillCircle(ctx, tail, SNAKE_RADIUS / 2, TAIL_COLOR)
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawSnakeTube(ctx: CanvasRenderingContext2D, pts: Vec2d[]) {
|
function drawSnakeCatmullRom(ctx: CanvasRenderingContext2D, bounds: Vec2d) {
|
||||||
ctx.beginPath();
|
const curve = catmullRom([snake[0], ...snake, snake[snake.length - 1]]);
|
||||||
ctx.moveTo(pts[0].x, pts[0].y);
|
|
||||||
for (let i = 1; i < pts.length; i++) ctx.lineTo(pts[i].x, pts[i].y);
|
ctx.strokeStyle = rgbToString(TAIL_COLOR);
|
||||||
ctx.strokeStyle = `#${HEAD_COLOR.toString(16).padStart(6, '0')}`;
|
|
||||||
ctx.lineWidth = SNAKE_RADIUS * 2;
|
ctx.lineWidth = SNAKE_RADIUS * 2;
|
||||||
ctx.lineCap = "round";
|
ctx.lineCap = "round";
|
||||||
ctx.lineJoin = "round";
|
ctx.lineJoin = "round";
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
let prev: Vec2d | null = null;
|
||||||
|
for (const p of curve) {
|
||||||
|
const w = new Vec2d(p.x, p.y).wrap(bounds);
|
||||||
|
// A wrap crossing shows up as a large jump in wrapped space.
|
||||||
|
const isWrapped = prev && (Math.abs(w.x - prev.x) > bounds.x / 2 || Math.abs(w.y - prev.y) > bounds.y / 2);
|
||||||
|
if (isWrapped) {
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.beginPath();
|
||||||
|
}
|
||||||
|
ctx.lineTo(w.x, w.y);
|
||||||
|
prev = w;
|
||||||
|
}
|
||||||
|
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
fillCircle(ctx, snake[0].wrap(bounds), SNAKE_RADIUS, HEAD_COLOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
function randomApple(bounds: Vec2d) {
|
function randomApple(bounds: Vec2d) {
|
||||||
@@ -102,7 +118,8 @@ function update(ctx: CanvasRenderingContext2D, time: number) {
|
|||||||
const t = (Math.sin(0.01 * time) + 1.0) * 0.5;
|
const t = (Math.sin(0.01 * time) + 1.0) * 0.5;
|
||||||
const apple_size = lerp(SNAKE_RADIUS, SNAKE_RADIUS * 0.9, t)
|
const apple_size = lerp(SNAKE_RADIUS, SNAKE_RADIUS * 0.9, t)
|
||||||
fillCircle(ctx, apple, apple_size, APPLE_COLOR); // draw apple
|
fillCircle(ctx, apple, apple_size, APPLE_COLOR); // draw apple
|
||||||
drawSnake(ctx, bounds);
|
if (draw_mode === "catmull") drawSnakeCatmullRom(ctx, bounds);
|
||||||
|
else drawSnake(ctx, bounds);
|
||||||
|
|
||||||
window.requestAnimationFrame((t) => update(ctx, t));
|
window.requestAnimationFrame((t) => update(ctx, t));
|
||||||
}
|
}
|
||||||
@@ -142,11 +159,15 @@ function init() {
|
|||||||
case "ArrowRight": case "d": new_direction = DIRECTIONS.right; break;
|
case "ArrowRight": case "d": new_direction = DIRECTIONS.right; break;
|
||||||
case "j": interpolation_factor++; break;
|
case "j": interpolation_factor++; break;
|
||||||
case "k": interpolation_factor = Math.max(1, interpolation_factor-1); break;
|
case "k": interpolation_factor = Math.max(1, interpolation_factor-1); break;
|
||||||
|
case "c":
|
||||||
|
draw_mode = draw_mode === "catmull" ? "linear" : "catmull";
|
||||||
|
setStatusLine(`render mode: ${draw_mode === "catmull" ? "catmull-rom" : "normal"}`);
|
||||||
|
break;
|
||||||
case "m":
|
case "m":
|
||||||
mouse_control = !mouse_control;
|
mouse_control = !mouse_control;
|
||||||
setStatusLine(`mouse control: ${mouse_control ? "on" : "off"}`);
|
setStatusLine(`mouse control: ${mouse_control ? "on" : "off"}`);
|
||||||
break;
|
break;
|
||||||
case "Space": case "p":
|
case " ": case "p":
|
||||||
paused = !paused;
|
paused = !paused;
|
||||||
setStatusLine(paused ? "paused" : "", null);
|
setStatusLine(paused ? "paused" : "", null);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ export default class Vec2d implements Point {
|
|||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromPoint(p: Point) {
|
||||||
|
return new Vec2d(p.x, p.y);
|
||||||
|
}
|
||||||
|
|
||||||
static fromPolar(r: number, theta: number) {
|
static fromPolar(r: number, theta: number) {
|
||||||
return new Vec2d(r * Math.cos(theta), r * Math.sin(theta));
|
return new Vec2d(r * Math.cos(theta), r * Math.sin(theta));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user