Physics stage 2: colliders gain the shapes capsule and polygon, a polygon keeps its corners in the property points, turns, scales and mirrors with its object and is edited in the scene view (drag a corner, click an edge to insert one, double-click to delete, at least three stay, each step one undo step), a concave polygon is split into convex parts, a self-intersecting one is drawn red and collides with nothing, Rapier gets a capsule and one convex collider per part on the same body, and Move and the editor run use our own f64 geometry for the new shapes, where on a tie between two normals the one that blocks the move least wins, so a box no longer stops at the foot of a slope (#151); first version of a collision outline per texture frame, edited in the texture tab with the new Collision toggle, which a plain tile blocks with instead of its rectangle (#141, to be reworked)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1b5e386b60
commit
32057a96fc
25 files changed
+2341
-122
No files matched your search
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Editing an outline by its corners (#141, #151), the same in the Scene view (a polygon collider)
|
||||
* and in the Texture tab (a frame's collision outline): drag a corner, press on an edge to put a
|
||||
* corner there, double-click a corner to take it away. Pure, so the rules are testable without a view.
|
||||
*/
|
||||
import type { Vec2 } from '../core/model/Properties';
|
||||
|
||||
/** What lies under the pointer: a corner, or the edge from corner `index` to the next one. */
|
||||
export type PolygonGrip = { kind: 'corner'; index: number } | { kind: 'edge'; index: number; at: Vec2 };
|
||||
|
||||
/** Fewer corners than this are no polygon; the last three cannot be taken away. */
|
||||
export const MIN_CORNERS = 3;
|
||||
|
||||
function closestOnSegment(p: Vec2, a: Vec2, b: Vec2): Vec2 {
|
||||
const ab = { x: b.x - a.x, y: b.y - a.y };
|
||||
const length = ab.x * ab.x + ab.y * ab.y;
|
||||
const t = length > 0 ? Math.min(1, Math.max(0, ((p.x - a.x) * ab.x + (p.y - a.y) * ab.y) / length)) : 0;
|
||||
return { x: a.x + ab.x * t, y: a.y + ab.y * t };
|
||||
}
|
||||
|
||||
/**
|
||||
* The corner or edge within `tolerance` of `p`, the nearest one; a corner wins over an edge, since
|
||||
* every corner lies on two edges. An outline of one or two corners has no edge to press on yet.
|
||||
*/
|
||||
export function gripAt(points: readonly Vec2[], p: Vec2, tolerance: number): PolygonGrip | null {
|
||||
let best: PolygonGrip | null = null;
|
||||
let nearest = tolerance;
|
||||
for (const [index, corner] of points.entries()) {
|
||||
const distance = Math.hypot(corner.x - p.x, corner.y - p.y);
|
||||
if (distance > nearest) continue;
|
||||
best = { kind: 'corner', index };
|
||||
nearest = distance;
|
||||
}
|
||||
if (best || points.length < MIN_CORNERS) return best;
|
||||
for (const [index, a] of points.entries()) {
|
||||
const at = closestOnSegment(p, a, points[(index + 1) % points.length]!);
|
||||
const distance = Math.hypot(at.x - p.x, at.y - p.y);
|
||||
if (distance > nearest) continue;
|
||||
best = { kind: 'edge', index, at };
|
||||
nearest = distance;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/** A corner put at `at` on the edge after corner `index`; it becomes corner `index + 1`. */
|
||||
export function withCornerAfter(points: readonly Vec2[], index: number, at: Vec2): Vec2[] {
|
||||
return [...points.slice(0, index + 1), { ...at }, ...points.slice(index + 1)];
|
||||
}
|
||||
|
||||
export function withCornerMoved(points: readonly Vec2[], index: number, to: Vec2): Vec2[] {
|
||||
return points.map((p, i) => (i === index ? { ...to } : { ...p }));
|
||||
}
|
||||
|
||||
/** The outline without corner `index`; null when that would leave it fewer than `min` corners. */
|
||||
export function withoutCorner(points: readonly Vec2[], index: number, min = MIN_CORNERS): Vec2[] | null {
|
||||
if (points.length - 1 < min) return null;
|
||||
return points.filter((_, i) => i !== index).map((p) => ({ ...p }));
|
||||
}
|
||||
Reference in new issue
Block a user