This commit is contained in:
2026-08-19 17:17:46 +02:00
parent a40f10ab22
commit 2ae236cc67
5 changed files with 64 additions and 25 deletions
+12 -7
View File
@@ -1,4 +1,4 @@
import { Point, lerpPoint, drawCircle, drawLine, drawDashedLine, resizeCanvas } from "./common.js" import { Point, lerpPoint, drawCircle, drawLine, drawDashedLine, resizeCanvas, drawCurve } from "./common.js"
let points: Point[] = []; let points: Point[] = [];
@@ -17,7 +17,8 @@ function update(ctx: CanvasRenderingContext2D, time: number) {
drawDashedLine(ctx, ab, bc, 0xFF0000); drawDashedLine(ctx, ab, bc, 0xFF0000);
drawCircle(ctx, ab, 1, 0xFF0000); drawCircle(ctx, ab, 1, 0xFF0000);
drawCircle(ctx, bc, 1, 0xFF0000); drawCircle(ctx, bc, 1, 0xFF0000);
drawCircle(ctx, abc, 1, 0xFFFFFF);
drawCircle(ctx, abc, 1, 0xFFFFFF); // Point on the bezier curve
} else if (points.length === 4) { } else if (points.length === 4) {
const [a, b, c, d] = points; const [a, b, c, d] = points;
const ab = lerpPoint(a, b, p); const ab = lerpPoint(a, b, p);
@@ -35,7 +36,8 @@ function update(ctx: CanvasRenderingContext2D, time: number) {
drawCircle(ctx, cd, 1, 0xFF0000); drawCircle(ctx, cd, 1, 0xFF0000);
drawCircle(ctx, abc, 1, 0xFFFF00); drawCircle(ctx, abc, 1, 0xFFFF00);
drawCircle(ctx, bcd, 1, 0xFFFF00); drawCircle(ctx, bcd, 1, 0xFFFF00);
drawCircle(ctx, abcd, 1, 0xFFFFFF);
drawCircle(ctx, abcd, 1, 0xFFFFFF); // Point on the bezier curve
} }
for (let i = 0; i < points.length - 1; ++i) { for (let i = 0; i < points.length - 1; ++i) {
@@ -60,10 +62,13 @@ function init() {
resizeCanvas(ctx); // Init canvas to full window size resizeCanvas(ctx); // Init canvas to full window size
canvas.addEventListener("click", (evt) => { canvas.addEventListener("click", (evt) => {
const {clientX, clientY} = evt; const rect = canvas.getBoundingClientRect();
if (points.length < 4) { if (points.length < 4) {
points.push({x: clientX, y: clientY}); points.push({
} x: evt.clientX - rect.left,
y: evt.clientY - rect.top,
});
}
}); });
window.addEventListener('resize', () => resizeCanvas(ctx)); window.addEventListener('resize', () => resizeCanvas(ctx));
+20 -2
View File
@@ -3,9 +3,27 @@
<head> <head>
<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>Metaballs</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #000000;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
#canvas {
display: block;
width: 100%;
height: 100%;
}
</style>
</head> </head>
<body style="margin: 0; padding 0; overflow: hidden; background-color: #000000"> <body>
<canvas id="canvas" tabindex="0"></canvas> <canvas id="canvas" tabindex="0"></canvas>
<script type="module" src="./metaballs.js"></script> <script type="module" src="./metaballs.js"></script>
</body> </body>
+15 -9
View File
@@ -1,3 +1,5 @@
import { resizeCanvas } from "./common.js";
let pos = { x: 0, y: 0 } let pos = { x: 0, y: 0 }
function init() { function init() {
@@ -5,20 +7,25 @@ function init() {
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");
resizeCanvas(ctx);
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
canvas.onmousemove = (evt) => { canvas.onmousemove = (evt) => {
const { clientX, clientY } = evt; const rect = canvas.getBoundingClientRect();
//const index = (clientY * canvas.width + clientX) * 4; //const index = (clientY * canvas.width + clientX) * 4;
/*console.log("color", /*console.log("color",
pixels[index], pixels[index],
pixels[index+1], pixels[index+1],
pixels[index+2]);*/ pixels[index+2]);*/
pos = { x: clientX, y: clientY }; pos = {
x: (evt.clientX - rect.left) * canvas.width / rect.width,
y: (evt.clientY - rect.top) * canvas.height / rect.height,
};
} }
// A canvas does not normally receive resize events. Use a resize observer
const observer = new ResizeObserver(() => resizeCanvas(ctx));
observer.observe(canvas);
window.requestAnimationFrame(t => update(ctx, t)); window.requestAnimationFrame(t => update(ctx, t));
} }
@@ -34,15 +41,15 @@ function update(ctx: CanvasRenderingContext2D, timestamp: number) {
const d1 = 6000 / ((x - width/2)*(x - width/2) + (y - height/2)*(y - height/2)); const d1 = 6000 / ((x - width/2)*(x - width/2) + (y - height/2)*(y - height/2));
const d2 = 8000 / ((x - pos.x)*(x - pos.x) + (y - pos.y)*(y - pos.y)); const d2 = 8000 / ((x - pos.x)*(x - pos.x) + (y - pos.y)*(y - pos.y));
if (d1 + d2 >= 0.90 && d1 + d2 <= 1.00) { if (d1 + d2 >= 0.90 && d1 + d2 <= 1.00) {
pixels[index] = 255*d1; pixels[index] = 255 * d1;
pixels[index+1] = 0; pixels[index+1] = 0;
pixels[index+2] = 255*d2; pixels[index+2] = 255 * d2;
pixels[index+3] = 255; pixels[index+3] = 255;
} else { } else {
pixels[index] = 0; pixels[index] = 0;
pixels[index+1] = 0; pixels[index+1] = 0;
pixels[index+2] = 0; pixels[index+2] = 0;
pixels[index+3] = 255; pixels[index+3] = 0;
} }
} }
} }
@@ -52,4 +59,3 @@ function update(ctx: CanvasRenderingContext2D, timestamp: number) {
} }
init(); init();
+2 -1
View File
@@ -99,7 +99,8 @@ function init() {
canvas.onmousemove = (evt) => { canvas.onmousemove = (evt) => {
const {clientX, clientY} = evt; const {clientX, clientY} = evt;
target = new Vec2d(clientX, clientY); const rect = canvas.getBoundingClientRect();
target = new Vec2d(clientX - rect.left, clientY - rect.top);
} }
canvas.onclick = () => { canvas.onclick = () => {
+9
View File
@@ -38,9 +38,14 @@ export default class Vec2d implements Point {
normalize(): Vec2d { normalize(): Vec2d {
const len = this.length(); const len = this.length();
if (len === 0) return new Vec2d(0, 0); // avoid division by zero
return new Vec2d(this.x / len, this.y / len); return new Vec2d(this.x / len, this.y / len);
} }
neg(): Vec2d {
return new Vec2d(-this.x, -this.y);
}
scale(scalar: number): Vec2d { scale(scalar: number): Vec2d {
return new Vec2d(this.x * scalar, this.y * scalar); return new Vec2d(this.x * scalar, this.y * scalar);
} }
@@ -61,6 +66,10 @@ export default class Vec2d implements Point {
return this.sub(other).length(); return this.sub(other).length();
} }
dot(other: Vec2d): number {
return this.x * other.x + this.y * other.y;
}
lerp(other: Vec2d, t: number): Vec2d { lerp(other: Vec2d, t: number): Vec2d {
// (1-t)*A + B*t // (1-t)*A + B*t
return this.scale(1 - t).add(other.scale(t)); return this.scale(1 - t).add(other.scale(t));