UNPKG

pocket-physics

Version:

Verlet physics extracted from pocket-ces demos

181 lines (180 loc) 6.7 kB
import { add, sub, v2, dot, scale, distance, copy, normalize } from "./v2"; import { collideCircleCircle } from "./collide-circle-circle"; // Preallocations const edgeDir = v2(); const edge = v2(); const prevEdge = v2(); const hypo = v2(); const epDiff = v2(); const correction = v2(); const collisionPoint = v2(); const tunnelPoint = v2(); const ep = { cpos: v2(), ppos: v2() }; const epBefore = { cpos: v2(), ppos: v2() }; export function collideCircleEdge(circle, radius3, mass3, endpoint1, mass1, endpoint2, mass2, preserveInertia, damping) { // Edge direction (edge in local space) sub(edge, endpoint2.cpos, endpoint1.cpos); // Normalize collision edge (assume collision axis is edge) normalize(edgeDir, edge); // Vector from endpoint1 to particle sub(hypo, circle.cpos, endpoint1.cpos); // Where is the particle on the edge, before, after, or on? // Also used for interpolation later. const projection = dot(edge, hypo); const maxDot = dot(edge, edge); const edgeMag = Math.sqrt(maxDot); // Colliding beyond the edge... if (projection < 0 || projection > maxDot) return; // Create interpolation factor of where point closest // to particle is on the line. const t = projection / maxDot; const u = 1 - t; // Find the point of collision on the edge. scale(collisionPoint, edgeDir, t * edgeMag); add(collisionPoint, collisionPoint, endpoint1.cpos); const dist = distance(collisionPoint, circle.cpos); // Bail if point and edge are too far apart. if (dist > radius3) return; // Distribute mass of colliding point into two fake points // and use those to collide against each endpoint independently. const standinMass1 = u * mass3; const standinMass2 = t * mass3; const standin1 = { cpos: v2(), ppos: v2() }; const standin2 = { cpos: v2(), ppos: v2() }; // Slide standin1 along edge to be in front of endpoint1 scale(standin1.cpos, edgeDir, t * edgeMag); sub(standin1.cpos, circle.cpos, standin1.cpos); scale(standin1.ppos, edgeDir, t * edgeMag); sub(standin1.ppos, circle.ppos, standin1.ppos); // Slide standin2 along edge to be in front of endpoint2 scale(standin2.cpos, edgeDir, u * edgeMag); add(standin2.cpos, circle.cpos, standin2.cpos); scale(standin2.ppos, edgeDir, u * edgeMag); add(standin2.ppos, circle.ppos, standin2.ppos); const standin1Before = { cpos: v2(), ppos: v2() }; const standin2Before = { cpos: v2(), ppos: v2() }; // Stash state of standins copy(standin1Before.cpos, standin1.cpos); copy(standin1Before.ppos, standin1.ppos); copy(standin2Before.cpos, standin2.cpos); copy(standin2Before.ppos, standin2.ppos); const edgeRadius = 0; // Collide standins with endpoints collideCircleCircle(standin1, radius3, standinMass1, endpoint1, edgeRadius, mass1, preserveInertia, damping); collideCircleCircle(standin2, radius3, standinMass2, endpoint2, edgeRadius, mass2, preserveInertia, damping); const standin1Delta = { cpos: v2(), ppos: v2() }; const standin2Delta = { cpos: v2(), ppos: v2() }; // Compute standin1 cpos change sub(standin1Delta.cpos, standin1.cpos, standin1Before.cpos); // Compute standin2 cpos change sub(standin2Delta.cpos, standin2.cpos, standin2Before.cpos); scale(standin1Delta.cpos, standin1Delta.cpos, u); scale(standin2Delta.cpos, standin2Delta.cpos, t); // Apply cpos changes to point3 add(circle.cpos, circle.cpos, standin1Delta.cpos); add(circle.cpos, circle.cpos, standin2Delta.cpos); if (!preserveInertia) return; // TODO: instead of adding diff, get magnitude of diff and scale // in reverse direction of standin velocity from point3.cpos because // that is what circlecircle does. // Compute standin1 ppos change sub(standin1Delta.ppos, standin1.ppos, standin1Before.ppos); // Compute standin2 ppos change sub(standin2Delta.ppos, standin2.ppos, standin2Before.ppos); scale(standin1Delta.ppos, standin1Delta.ppos, u); scale(standin2Delta.ppos, standin2Delta.ppos, t); // Apply ppos changes to point3 add(circle.ppos, circle.ppos, standin1Delta.ppos); add(circle.ppos, circle.ppos, standin2Delta.ppos); } function snapshotDebug(name, particles = [], points = []) { const cvs = document.createElement("canvas"); const ctx = cvs.getContext("2d"); document.body.appendChild(cvs); // let minX = Number.MAX_SAFE_INTEGER; // let maxX = Number.MIN_SAFE_INTEGER; // let minY = Number.MAX_SAFE_INTEGER; // let maxY = Number.MIN_SAFE_INTEGER; // for (let i = 0; i < particles.length; i++) { // const particle = particles[i]; // minX = Math.min( // minX, // particle.cpos.x - particle.radius, // particle.ppos.x - particle.radius // ); // maxX = Math.max( // maxX, // particle.cpos.x + particle.radius, // particle.ppos.x + particle.radius // ); // minY = Math.min( // minY, // particle.cpos.y - particle.radius, // particle.ppos.y - particle.radius // ); // maxY = Math.max( // maxY, // particle.cpos.y + particle.radius, // particle.ppos.y + particle.radius // ); // } // for (let i = 0; i < points.length; i++) { // const [, point] = points[i]; // minX = Math.min(minX, point.x, point.x); // maxX = Math.max(maxX, point.x, point.x); // minY = Math.min(minY, point.y, point.y); // maxY = Math.max(maxY, point.y, point.y); // } // cvs.width = maxX - minX; // cvs.height = maxY - minY; // ctx.translate(-minX, -minY); cvs.width = 800; cvs.height = 800; for (let i = 0; i < particles.length; i++) { const particle = particles[i]; ctx.fillStyle = "rgba(255, 0, 0, 0.5)"; ctx.beginPath(); ctx.arc(particle.ppos.x, particle.ppos.y, particle.radius, 0, Math.PI * 2, false); ctx.fill(); ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; ctx.beginPath(); ctx.arc(particle.cpos.x, particle.cpos.y, particle.radius, 0, Math.PI * 2, false); ctx.fill(); } for (let i = 0; i < points.length; i++) { const [name, point] = points[i]; ctx.fillStyle = "purple"; ctx.fillRect(point.x, point.y, 1, 1); ctx.fillText(`${name} (${point.x},${point.y})`, point.x + 1, point.y + 1); } // ctx.translate(minX, minY); ctx.fillStyle = "black"; ctx.fillText(name, 10, 10); }