diff --git a/html/images/space.svg b/html/images/space.svg index 51c4312..ca6da7f 100644 --- a/html/images/space.svg +++ b/html/images/space.svg @@ -198,17 +198,17 @@
-,- x,y velocity - + + + + + + + + + + +

     
x: -, @@ -1090,14 +1090,6 @@ function init() { time.innerText = "0"; } - -// const b = map.edges.map(({ edge }) => getEdgeCollisionBoundary(edge, shipRadius)); -// b.forEach(b => drawLine(b.xa, b.ya, b.xb, b.yb, "orange")); - -function distance(x1, y1, x2, y2) { - return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); -} - function drawTriangles(container, walls, [positionX, positionY]) { walls.forEach(pts => pts.forEach(([[x1, y1], [x2, y2]]) => { @@ -1109,93 +1101,6 @@ function drawTriangles(container, walls, [positionX, positionY]) { ); } -// Triangle has a clockwise orientation -function isClockwise([xa, ya], [xb, yb], [xc, yc]) { - // https://en.wikipedia.org/wiki/Curve_orientation#Practical_considerations - // Determinant for a convex polygon - const det = (+xb - +xa) * (+yc - +ya) - (+xc - +xa) * (+yb - +ya); - return det < 0; -} - -function isAcute([xa, ya], [xb, yb], [xc, yc]) { - const da = distance(xa, ya, xc, yc); - const db = distance(xb, yb, xc, yc); - const dc = distance(xa, ya, xb, yb); - - // https://en.wikipedia.org/wiki/Law_of_cosines - // Solve for angles alpha and beta with inverse cosine (arccosine) - const alpha = Math.acos((db ** 2 + dc ** 2 - da ** 2) / (2 * db * dc)); - const beta = Math.acos((da ** 2 + dc ** 2 - db ** 2) / (2 * da * dc)); - - return alpha < halfPi && beta < halfPi; -} - -function getForwardEdges(edges, { x, y }) { - return edges.filter(({ edge: { xa, ya, xb, yb } }) => - isClockwise([xa, ya], [xb, yb], [x, y]) && isAcute([xa, ya], [xb, yb], [x, y])); -} - -function getForwardCorners(corners, position, velocity) { - const { x: x1, y: y1 } = position; - const { x: x2, y: y2 } = velocity; - - const { x: vx, y: vy } = velocity; - - let perppts = {}; - - if (vx === 0 && vy === 0) { - // none - } else if (vx === 0 && vy > 0) { - perppts = { a: { x: x1 - 1, y: y1 }, b: { x: x1 + 1, y: y1 }}; - } else if (vx === 0 && vy < 0) { - perppts = { a: { x: x1 + 1, y: y1 }, b: { x: x1 - 1, y: y1 }}; - } else if (vy === 0 && vx > 0) { - perppts = { a: { x: x1, y: y1 + 1 }, b: { x: x1, y: y1 - 1 }}; - } else if (vy === 0 && vx < 0) { - perppts = { a: { x: x1, y: y1 - 1 }, b: { x: x1, y: y1 + 1 }}; - } else if (vy > 0 && vx > 0) { - const vslope = vy / vx; - const pslope = 1 / -vslope; - // Point-slope line equation - const pya = pslope * (x1 - 1) - pslope * x1 + y1; - const pyb = pslope * (x1 + 1) - pslope * x1 + y1; - - perppts = { a: { x: x1 - 1, y: pya }, b: { x: x1 + 1, y: pyb }}; - } else if (vy > 0 && vx < 0) { - const vslope = vy / vx; - const pslope = 1 / -vslope; - const pya = pslope * (x1 - 1) - pslope * x1 + y1; - const pyb = pslope * (x1 + 1) - pslope * x1 + y1; - - perppts = { a: { x: x1 - 1, y: pya }, b: { x: x1 + 1, y: pyb }}; - } else if (vy < 0 && vx > 0) { - const vslope = vy / vx; - const pslope = 1 / -vslope; - const pya = pslope * (x1 + 1) - pslope * x1 + y1; - const pyb = pslope * (x1 - 1) - pslope * x1 + y1; - - perppts = { a: { x: x1 + 1, y: pya }, b: { x: x1 - 1, y: pyb }}; - } else if (vy < 0 && vx < 0) { - const vslope = vy / vx; - const pslope = 1 / -vslope; - const pya = pslope * (x1 + 1) - pslope * x1 + y1; - const pyb = pslope * (x1 - 1) - pslope * x1 + y1; - - perppts = { a: { x: x1 + 1, y: pya }, b: { x: x1 - 1, y: pyb }}; - } else { - // - } - - const { a, b } = perppts; - // if (a && b) drawLine(a.x, a.y, b.x, b.y); - - return corners.filter(({ corner: c }) => { - if (!a || !b) return; - const det = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); - return det > 0; - }); -} - function updateTriangles([positionX, positionY]) { const delim = ' '; const className = 'clockwise-orientation'; @@ -1322,25 +1227,6 @@ function fireBullet(position, degrees) { setTimeout(() => appended.classList.add('fade'), 1000); } -function perpIntxn(baseSlope, xa, ya, xc, yc) { - let isx, isy; - - // base is vertical - if (baseSlope === -Infinity || baseSlope === Infinity) { - isx = xa; - isy = yc; - } else if (baseSlope === 0) { // base is horizontal - isx = xc; - isy = ya; - } else { - const altitudeSlope = 1 / -baseSlope; - isx = (-altitudeSlope * xc + yc + baseSlope * xa - ya) / (baseSlope - altitudeSlope); - isy = altitudeSlope * isx - altitudeSlope * xc + yc; - } - - return { x: isx, y: isy }; -} - function drawLine(xa, ya, xb, yb, color = "black") { const el = document.createElementNS(namespaceURIsvg, 'line'); el.setAttribute('x1', xa); @@ -1369,142 +1255,11 @@ function drawCircle(cx, cy, color = "black", r = 1) { return el; } -function slope({ xa, ya, xb, yb }) { - return (yb - ya) / (xb - xa); -} - function isLandable(edge) { return edge.xa < edge.xb && edge.ya === edge.yb; // return Object.is(slope(edge), +0); } -function getEdgeCollisionBoundary(edge, dist) { - const { xa, ya, xb, yb } = edge; - const length = distance(xa, ya, xb, yb); - const rise = yb - ya; - const run = xb - xa; - const riol = rise / length * dist; - const ruol = run / length * dist; - - return { xa: xa + riol, ya: ya - ruol, xb: xb + riol, yb: yb - ruol}; -} - -function detectEdgeCollision([xc, yc], [x, y], radius, gearDown) { - - return (collision) => { - if (xc === x && yc === y) return; - const { edge, wall } = collision; - const dist = edge.xa < edge.xb && edge.ya === edge.yb && gearDown ? radius + 1.5 : radius; - const edgeSeg = getEdgeCollisionBoundary(edge, dist); - const positionSeg = { xa: x, ya: y, xb: xc, yb: yc }; - - const { xa: x1, ya: y1, xb: x2, yb: y2 } = positionSeg; - const { xa: x3, ya: y3, xb: x4, yb: y4 } = edgeSeg; - // const { xa: x3, ya: y3, xb: x4, yb: y4 } = edge; - - // https://en.wikipedia.org/wiki/Intersection_(geometry)#Two_line_segments - // https://en.wikipedia.org/wiki/Cramer%27s_rule#Explicit_formulas_for_small_systems - // const denom = +((x2-x1)*(y4-y3)-(x4-x3)*(y2-y1)).toPrecision(13); - const x21 = +(x2-x1).toPrecision(13); - const y43 = +(y4-y3).toPrecision(13); - const x43 = +(x4-x3).toPrecision(13); - const y21 = +(y2-y1).toPrecision(13); - const denom = +(x21*y43-x43*y21).toPrecision(13); - const x31 = +(x3-x1).toPrecision(13); - const y31 = +(y3-y1).toPrecision(13); - - const s = +(+(x31*y43-x43*y31).toPrecision(13) / denom).toPrecision(13); - const t = +(-(x21*y31-x31*y21).toPrecision(13) / denom).toPrecision(13); - - // const s = +(((x3-x1)*(y4-y3)-(x4-x3)*(y3-y1))/denom).toPrecision(13); - // const t = +(-((x2-x1)*(y3-y1)-(x3-x1)*(y2-y1))/denom).toPrecision(13); - - - if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { - - const xs = (x1 + s * +(x2 - x1).toPrecision(13)); - const ys = (y1 + s * +(y2 - y1).toPrecision(13)); - collision.position = { x: +xs.toPrecision(13), y: +ys.toPrecision(13) }; - - return true; - } - // return roundedS >= 0 && roundedS <= 1 && roundedT >= 0 && roundedT <= 1; - return; - }; -} - -function detectCornerCollision([xc, yc], [x, y], radius) { - return c => { - if (xc === x && yc === y) return; - - const d = distance(c.corner.x, c.corner.y, xc, yc); - - if (d <= radius) return true; - - const positionSeg = { xa: xc, ya: yc, xb: x, yb: y }; - const posNormIntxn = perpIntxn(slope(positionSeg), x, y, c.corner.x, c.corner.y); - const cornerSeg = { xa: c.corner.x, ya: c.corner.y, xb: posNormIntxn.x, yb: posNormIntxn.y }; - - const { x: x0, y: y0 } = c.corner; - const { xa: x1, ya: y1, xb: x2, yb: y2 } = positionSeg; - const { xa: x3, ya: y3, xb: x4, yb: y4 } = cornerSeg; - - // https://en.wikipedia.org/wiki/Intersection_(geometry)#Two_line_segments - // https://en.wikipedia.org/wiki/Cramer%27s_rule#Explicit_formulas_for_small_systems - const s = ((x3-x1)*(y4-y3)-(x4-x3)*(y3-y1))/((x2-x1)*(y4-y3)-(x4-x3)*(y2-y1)); - const t = -((x2-x1)*(y3-y1)-(x3-x1)*(y2-y1))/((x2-x1)*(y4-y3)-(x4-x3)*(y2-y1)); - const roundedT = +t.toFixed(2); - - if (s >= 0 && roundedT <= 1) { - const xs = (x1 + s * (x2 - x1)); - const ys = (y1 + s * (y2 - y1)); - const xt = (x3 + roundedT * (x4 - x3)); - const yt = (y3 + roundedT * (y4 - y3)); - // [xs, ys] and [xt, yt] should be equal ([xs, ys] === [xy, yt]) - // (...or about equal, notwithstanding rounding errors) - const sCollisionPt = [xs, ys]; - const tCollisionPt = [xt, yt]; - // drawCircle(posNormIntxn.x, posNormIntxn.y, "red"); - // drawCircle(...tCollisionPt, "blue"); - } - - return s >= 0 && roundedT <= 1; - }; -} - -function detectCollision(currentPos, intendedPos, velocity, radius, { edges, corners }, gearDown) { - const { x: xc, y: yc } = intendedPos; - const [x, y] = currentPos; - // edges oriented clockwise with ship - const fwdEdges = getForwardEdges(edges, { x, y }) - const edgeColl = fwdEdges.find(detectEdgeCollision([xc, yc], [x, y], radius, gearDown)); - - if (edgeColl) return edgeColl; - - // corners ahead of ship - const fwdCorners = getForwardCorners(corners, { x, y }, velocity); - const cornersInPath = fwdCorners.filter(withinCollisionDistance({ x, y }, velocity, radius)); - const cornerColl = cornersInPath.find(detectCornerCollision([xc, yc], [x, y], radius)); - - if (cornerColl) return cornerColl; -} - -function withinCollisionDistance({ x: x1, y: y1 }, { x: x2, y: y2 }, distance) { - const diffx = x2; - const diffy = y2; - const detv = x2 * y1 - y2 * x1; - const dv = Math.sqrt(diffy ** 2 + diffx ** 2); - const slopev = slope({ xa: x1, ya: y1, xb: x1 + x2, yb: y1 + y2 }); - - return ({ corner: { x: x0, y: y0 }}) => { - const velNormIntxn = perpIntxn(slopev, x1, y1, x0, y0); - const dx = Math.max(x0, velNormIntxn.x) - Math.min(x0, velNormIntxn.x); - const dy = Math.max(y0, velNormIntxn.y) - Math.min(y0, velNormIntxn.y); - const d = Math.sqrt(dy ** 2 + dx ** 2); - return d <= distance; - }; -} - function cornerContactPosition(xc, yc, x, y, corner, cLength) { const positionSeg = { xa: xc, ya: yc, xb: x, yb: y }; const posNormIntxn = perpIntxn(slope(positionSeg), x, y, corner.x, corner.y); @@ -1628,7 +1383,7 @@ function updateShip(s, elapsed) { s.degrees = degrees + dDelta; current = s.collision; - s.collision = detectCollision([px, py], p, s.velocity, s.radius, map, s.gearDown); + // s.collision = detectCollision([px, py], p, s.velocity, s.radius, map, s.gearDown); // if (!current && s.collision) console.log("COLLISION", s.collision); legs.style.display = s.gearDown ? "initial" : "none";