6af56d2ade
Build and deploy / deploy (push) Successful in 37s
Premier mini-jeu compétitif du Tribunal (/course) : piste 2D générée de façon déterministe à partir de la date du jour (identique pour tout le monde, jamais stockée côté serveur), pilotage au joystick tactile virtuel, murs + obstacles qui ralentissent temporairement sans jamais stopper la course, fantômes du top 3 du jour. Chacun rejoue autant qu'il veut, seul le meilleur temps compte. Moteur de jeu maison (src/lib/chariot/) : canvas + requestAnimationFrame, sans dépendance externe, dans la continuité de l'existant (roulette). Navigation : nouvelle entrée "La Course du Char" (IconChariot), route protégée par le proxy comme le reste de l'app.
78 lines
2.1 KiB
TypeScript
78 lines
2.1 KiB
TypeScript
export type Vec2 = { x: number; y: number };
|
|
|
|
export function add(a: Vec2, b: Vec2): Vec2 {
|
|
return { x: a.x + b.x, y: a.y + b.y };
|
|
}
|
|
|
|
export function sub(a: Vec2, b: Vec2): Vec2 {
|
|
return { x: a.x - b.x, y: a.y - b.y };
|
|
}
|
|
|
|
export function scale(a: Vec2, s: number): Vec2 {
|
|
return { x: a.x * s, y: a.y * s };
|
|
}
|
|
|
|
export function length(a: Vec2): number {
|
|
return Math.hypot(a.x, a.y);
|
|
}
|
|
|
|
export function normalize(a: Vec2): Vec2 {
|
|
const len = length(a);
|
|
return len > 0 ? scale(a, 1 / len) : { x: 0, y: 0 };
|
|
}
|
|
|
|
export function dot(a: Vec2, b: Vec2): number {
|
|
return a.x * b.x + a.y * b.y;
|
|
}
|
|
|
|
export function rotate90(a: Vec2): Vec2 {
|
|
return { x: -a.y, y: a.x };
|
|
}
|
|
|
|
// Point le plus proche du point p sur le segment [a,b].
|
|
export function closestPointOnSegment(p: Vec2, a: Vec2, b: Vec2): { point: Vec2; t: number } {
|
|
const ab = sub(b, a);
|
|
const abLenSq = dot(ab, ab);
|
|
const t = abLenSq > 0 ? Math.max(0, Math.min(1, dot(sub(p, a), ab) / abLenSq)) : 0;
|
|
return { point: add(a, scale(ab, t)), t };
|
|
}
|
|
|
|
// Catmull-Rom uniforme : passe exactement par chaque point de contrôle,
|
|
// samplesPerSegment points intermédiaires entre chaque paire de points.
|
|
export function catmullRom(points: Vec2[], samplesPerSegment: number): Vec2[] {
|
|
if (points.length < 2) return points.slice();
|
|
|
|
const result: Vec2[] = [];
|
|
const at = (i: number) => points[Math.max(0, Math.min(points.length - 1, i))];
|
|
|
|
for (let i = 0; i < points.length - 1; i++) {
|
|
const p0 = at(i - 1);
|
|
const p1 = at(i);
|
|
const p2 = at(i + 1);
|
|
const p3 = at(i + 2);
|
|
|
|
for (let s = 0; s < samplesPerSegment; s++) {
|
|
const t = s / samplesPerSegment;
|
|
const t2 = t * t;
|
|
const t3 = t2 * t;
|
|
result.push({
|
|
x:
|
|
0.5 *
|
|
(2 * p1.x +
|
|
(-p0.x + p2.x) * t +
|
|
(2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 +
|
|
(-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3),
|
|
y:
|
|
0.5 *
|
|
(2 * p1.y +
|
|
(-p0.y + p2.y) * t +
|
|
(2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 +
|
|
(-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3),
|
|
});
|
|
}
|
|
}
|
|
|
|
result.push(points[points.length - 1]);
|
|
return result;
|
|
}
|