Files
miti.sh/html/images/space.svg

1115 lines
36 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="-200 -150 400 300" version="1.1" xmlns="http://www.w3.org/2000/svg">
<!-- <svg viewBox="-10 -10 60 60" version="1.1" xmlns="http://www.w3.org/2000/svg"> -->
<style>
foreignObject {
font-size: 4pt;
font-family: courier;
color: white;
}
#info {
position: absolute;
right: 0px;
padding: 1px;
}
#pointer {
position: absolute;
right: 0px;
bottom: 0px;
padding: 1px;
}
rect#bg {
fill: gray;
}
.ship circle {
fill: white;
}
circle.bullet {
fill: yellow;
}
#triangles polygon {
fill-opacity: 0.2;
stroke-width: 1px;
fill: none;
stroke: none;
}
#triangles polygon.clockwise-orientation {
fill: white;
stroke: red;
}
#triangles polygon.obtuse {
stroke: orangered;
stroke-dasharray: 5 10;
}
#triangles polygon.anti-clockwise {
stroke: orange;
stroke-dasharray: 1 5;
}
#edges line {
stroke: gold;
}
#legs {
display: none;
}
.wall {
opacity: 0.5;
}
.wall.inverse {
fill: gray;
}
line:not(#cannon) {
stroke: green;
stroke-width: 0.5px;
}
line#velocity-indicator {
stroke: blue;
/* stroke-width: 1px; */
}
line#acceleration-indicator {
stroke: maroon;
/* stroke-width: 0.5px; */
}
#lines circle {
fill: purple;
opacity: 0.2;
r: 5px;
}
</style>
<rect id="bg" x="-200" y="-150" width="400" height="300"/>
<!-- <polygon class="wall inverse" points="-180,-40 -170,-120 40,-130 170,-120 180,40 170,130 -40,130 -170,120" /> -->
<!-- <polygon class="wall inverse" points="-160,-40 -170,-120 40,-110 170,-120 160,40 170,130 -40,110 -170,120" /> -->
<g id="player" class="ship">
<line id="cannon" x1="0" y1="0" x2="0" y2="8" stroke="black"/>
<circle id="body" cx="0" cy="0" r="5"/>
<line id="velocity-indicator" x1="0" y1="0" x2="0" y2="0"/>
<line id="acceleration-indicator" x1="0" y1="0" x2="0" y2="0"/>
<g id="legs">
<path d="M 3 2 l 2 2 v 2 m -2 0 h 4" stroke="black" fill="none" />
<path d="M -3 2 l -2 2 v 2 m -2 0 h 4" stroke="black" fill="none" />
</g>
</g>
<!-- <polygon class="wall" points="20,20 30,20 40,40 20,40" /> -->
<!-- <polygon class="wall" points="20,20 50,20 70,40 70,70 50,90 20,90 0,70 0,40" /> -->
<!-- <polygon class="wall" points="-10,-10 -20,-10 -20,-20 -10,-20" /> -->
<!-- <polygon class="wall" points="-50,-50 -60,-50 -60,-60 -50,-60" /> -->
<polygon class="wall" points="10,-50 3,-50 3,-60 10,-60" />
<polygon class="wall" points="-10,50 -3,50 -3,60 -10,60" />
<!-- <polygon class="wall" points="-10,20 10,10 10,20" /> -->
<!-- <polygon class="wall" points="20,20 40,20 40,40 20,40" /> -->
<!-- <polygon class="wall" points="10,10 20,10 20,20 10,20" /> -->
<!-- <polygon class="wall" points="20,-50 -50,-50 -60,-70 -50,-100 80,-100 80,-90 -20,-90 -20,-60 40,-60 40,40 20,40" /> -->
<!-- <polygon class="wall" points="-10,-30 -10,-40 30,-50 60,-30 80,0 150,0 150,10 60,50 -10,40 -20,20 20,20 20,-20" /> -->
<!-- <g> -->
<!-- <polygon class="wall" points="-130,-80 -40,-70 -70,-10" /> -->
<!-- <polygon class="wall" points="50,70 90,-10 130,70" /> -->
<!-- </g> -->
<!-- <g> -->
<!-- <polygon class="wall" points="-130,-80 -40,-70 -70,-10" /> -->
<!-- <polygon class="wall" points="50,70 90,-10 130,70" /> -->
<!-- <polygon class="wall" points="-130,100 -70,50 -40,110" /> -->
<!-- </g> -->
<!-- <g> -->
<!-- <polygon class="wall" points="-5,-25 -20,-40 20,-40 5,-25" /> -->
<!-- <polygon class="wall" points="-30,20 -20,30 -20,50 -50,20" /> -->
<!-- <polygon class="wall" points="20,30 30,20 50,20 20,50" /> -->
<!-- </g> -->
<g id="triangles"></g>
<g id="edges"></g>
<g id="lines"></g>
<g id="bullets"></g>
<foreignObject x="-200" y="-150" width="100%" height="100%">
<div id="info" xmlns="http://www.w3.org/1999/xhtml">
<span id="time" xmlns="http://www.w3.org/1999/xhtml">0</span> s
<span id="fps" xmlns="http://www.w3.org/1999/xhtml">-</span> fps
</div>
<ul xmlns="http://www.w3.org/1999/xhtml">
<li xmlns="http://www.w3.org/1999/xhtml">bounce from collisions</li>
<li xmlns="http://www.w3.org/1999/xhtml">gravity</li>
<li xmlns="http://www.w3.org/1999/xhtml">fall off screen after crash</li>
<li xmlns="http://www.w3.org/1999/xhtml">make ship a helicopter</li>
<li xmlns="http://www.w3.org/1999/xhtml">use paths for walls</li>
<li xmlns="http://www.w3.org/1999/xhtml">no walls</li>
<li xmlns="http://www.w3.org/1999/xhtml">stop reading data from elements</li>
<li xmlns="http://www.w3.org/1999/xhtml">ability to land</li>
<li xmlns="http://www.w3.org/1999/xhtml">limited fuel</li>
<li xmlns="http://www.w3.org/1999/xhtml">additional cannon firing modes</li>
<li xmlns="http://www.w3.org/1999/xhtml">only light up the collided wall</li>
<li xmlns="http://www.w3.org/1999/xhtml">keep ship position at 0,0 actual</li>
<li xmlns="http://www.w3.org/1999/xhtml">only start on movement not just any keypress</li>
</ul>
<pre id="debug" xmlns="http://www.w3.org/1999/xhtml"></pre>
<div id="pointer" xmlns="http://www.w3.org/1999/xhtml">
x: <span class="x" xmlns="http://www.w3.org/1999/xhtml">-</span>,
y: <span class="y" xmlns="http://www.w3.org/1999/xhtml">-</span>
</div>
</foreignObject>
<script type="text/javascript">//<![CDATA[
const namespaceURIsvg = 'http://www.w3.org/2000/svg';
const degsRegex = /(-?\d*\.{0,1}\d+)deg/g;
const regex = /(-?\d*\.{0,1}\d+)px/g;
const bullets = [];
const halfPi = Math.PI / 2;
const maxSpeed = 100;
const drawCollisionLines = false;
let previous, zero, frameCount = 0;
// let position = [0, 0]; // meters
// let velocity = [0, 0]; // meters per second
// let acceleration = [0, 0]; // meters per second per second
let position; // meters
let velocity; // meters per second
let acceleration; // meters per second per second
let rotate = 0;
let mult = 20;
const s = {
position: { x: 0, y: 0 },
// velocity: { x: 0, y: 0 },
velocity: { x: 1, y: 1 },
// velocity: { x: 10, y: -10 },
// velocity: { x: -10, y: 10 },
// velocity: { x: 0, y: 10 },
// velocity: { x: 10, y: 0 },
// velocity: { x: 0, y: -5000 },
// velocity: { x: 0.25 * mult, y: 1 * mult },
// velocity: { x: 0, y: 1 * mult },
acceleration: { x: 0, y: 0 },
rotate: 0,
collision: null,
isLanded: false,
gearDown: false,
node: null
};
let friction = 0;
let rotationSpeed = 0.25;
let started = false;
let restart = false;
let isReadingKeys = true;
const svg = document.querySelector('svg');
const bg = svg.querySelector('#bg');
const fps = document.querySelector("#fps");
const time = document.querySelector("#time");
const debug = document.querySelector("#debug");
const ship = document.querySelector(".ship");
s.node = ship;
const gun = ship.querySelector('#cannon');
const shipBody = ship.querySelector("#body");
const shipRadius = +shipBody.getAttribute('r');
const legs = ship.querySelector("#legs");
// const walls = document.querySelectorAll('.wall:not(.inverse)');
const walls = document.querySelectorAll('.wall');
const bulletsContainer = document.querySelector("#bullets");
const triangleContainer = document.querySelector('#triangles');
const linesContainer = document.querySelector("#lines");
const edgeContainer = document.querySelector('#edges');
const velIndic = document.querySelector('#velocity-indicator');
const acclIndic = document.querySelector('#acceleration-indicator');
const bulletPt = svg.createSVGPoint();
const cornerPt = svg.createSVGPoint();
const allWallCorners = [...walls].map(wall => {
const cs = wall.getAttribute('points').split(' ').map(coords => {
const [x, y] = coords.split(',');
return [+x, +y];
});
return wall.classList.contains("inverse") ? cs.reverse() : cs;
});
// console.log("allWallCorners", allWallCorners);
const ws = [...walls].map(node => {
return {
node,
corners: node.getAttribute('points').split(' ').map(coords => {
const [x, y] = coords.split(',');
const pt = svg.createSVGPoint();
pt.x = +x;
pt.y = +y;
return pt;
})
};
});
const edgeszz = [...walls].map(wall => {
const es = wall.getAttribute('points').split(' ').map((coords, i, arr) => {
const [x, y] = coords.split(',');
// return [+x, +y];
// const [xb, yb] = arr[i]++;
const [xb, yb] = arr[(i + 1) % arr.length].split(',');
// console.log("coords", { xa: +x, ya: +y, xb: +xb, yb: +yb, inverse: wall.classList.contains("inverse") });
// return `${+x},${+y} ${+xb},${+yb}`;
return {
xa: +x,
ya: +y,
xb: +xb,
yb: +yb,
};
});
// return wall.classList.contains("inverse") ? cs.reverse() : cs;
return { node: wall, edges: es };
});
const allEdgePts = allWallCorners.map(w =>
w.map((pt, i, arr) => [pt, arr[(i + 1) % arr.length]])
);
let allStartingEdges;
init(allEdgePts);
function distance(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
function drawAllEdges(walls) {
walls.forEach(edges => drawEdges(edges));
}
function drawEdges(pts) {
let edges = pts.map(e => e.join(' '));
edges.forEach(e => {
const [x, y] = e.split(' ');
const [x1, y1] = x.split(',');
const [x2, y2] = y.split(',');
const el = document.createElementNS(namespaceURIsvg, 'line');
el.setAttribute('x1', x1);
el.setAttribute('y1', y1);
el.setAttribute('x2', x2);
el.setAttribute('y2', y2);
edgeContainer.appendChild(el)
});
}
function drawTriangles(container, walls, [positionX, positionY]) {
walls.forEach(pts =>
pts.forEach(([[x1, y1], [x2, y2]]) => {
const el = document.createElementNS(namespaceURIsvg, 'polygon');
const attr = `${x1},${y1} ${x2},${y2} ${positionX},${positionY}`
el.setAttribute('points', attr);
container.appendChild(el);
})
);
}
// 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 findEdges(verts, [xc, yc]) {
return verts.reduce((acc, [a, b]) => {
const isFound = [isClockwise, isAcute].every(c => c(a, b, [xc, yc]));
// return isFound ? [...acc, `${xa},${ya} ${xb},${yb}`] : acc;
return isFound ? [...acc, `${a[0]},${a[1]} ${b[0]},${b[1]}`] : acc;
}, []);
}
// function findAllEdges(verts, [xc, yc] = [0, 0]) {
function findAllEdges(verts, [xc, yc]) {
return verts.reduce((acc, points) => {
points.forEach(([a, b]) => {
const isFound = [isClockwise, isAcute].every(c => c(a, b, [xc, yc]));
if (isFound) acc.push(`${a[0]},${a[1]} ${b[0]},${b[1]}`);
});
return acc;
}, []);
}
function getCollisionEdges(walls, [xc, yc]) {
return walls.reduce((acc, wall) => {
return [...acc, ...wall.edges.filter(({ xa, ya, xb, yb }) => {
return [isClockwise, isAcute].every(c => c([xa, ya], [xb, yb], [xc, yc]));
}).map(e => { return { edge: e, node: wall }; })];
}, []);
}
// function getCollisionCorners(ws, { x: px, y: py }, { x: vx, y: vy }) {
function getCollisionCorners(ws, { x: x1, y: y1 }, { x: x2, y: y2 }) {
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 ws.reduce((acc, w) => {
const filtered = w.corners.filter(c => {
const { x: x0, y: y0 } = c;
const velNormIntxn = perpIntxn(slopev, x1, y1, x0, y0);
// const d = distance(velNormIntxn.x, velNormIntxn.y , x0, y0);
// https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_two_points
const d = Math.abs(diffy * x0 - diffx * y0 + detv) / dv;
// console.log("corner", c, "d", d, "shipRadius", shipRadius);
return d <= shipRadius;
}).map(c => ({ corner: c, node: w.node }));
// });
return [...acc, ...filtered];
}, []);
}
function getForwardCollisionCorners(walls, position, velocity) {
const { x: x1, y: y1 } = position;
const { x: x2, y: y2 } = velocity;
// const a = { x: -100, y: 0 };
// const b = { x: 100, y: 0 };
console.log("VELOCITY", velocity);
const { x: vx, y: vy } = velocity;
console.log("velocity", velocity, "position", position);
let perppts;
if (vx === 0 && yx === 0) {
// none
} else if (vx === 0 && vy > 0) {
} else if (vx === 0 && vy < 0) {
} else if (vy === 0 && vx > 0) {
} else if (vy === 0 && vx < 0) {
} else if (vy > 0 && vx > 0) {
const vslope = vy / vx;
const pslope = 1 / -vslope;
// Point-slope line equation
// y y1 = m(x x1)
// y y1 = mx - mx1
// y = mx - mx1 + y1
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) {
} else if (vy < 0 && vx > 0) {
} else if (vy < 0 && vx < 0) {
} else {
//
}
// const slopev = slope({ xa: x1, ya: y1, xb: x1 + x2, yb: y1 + y2 });
// console.log("SLOPE", slopev);
// let slopeperp;
//
// if (slopev === -Infinity) {
// slopeperp = 0;
// } else if (slopev === Infinity) {
// slopeperp = -0;
// } else if (Object.is(slopev, -0)) {
// slopeperp = Infinity;
// } else if (slopev === 0) {
// slopeperp = -Infinity;
// } else {
// slopeperp = 1 / -slopev;
// }
//
//
// if (Object.is(slopeperp, 0)) {
// perppts = { a: { x: x1 - 1, y: y1 }, b: { x: x1 + 1, y: y1 }};
// } else if (Object.is(slopeperp, -0)) {
// perppts = { a: { x: x1 + 1, y: y1 }, b: { x: x1 - 1, y: y1 }};
// } else if (slopeperp === Infinity) {
// perppts = { a: { x: x1, y: y1 + 1 }, b: { x: x1, y: y1 - 1 }};
// } else if (slopeperp === -Infinity) {
// perppts = { a: { x: x1, y: y1 - 1 }, b: { x: x1, y: y1 + 1 }};
// } else {
// if (y2 < 0)
// perppts = { a: { x: x1 - 1, y: y1 }, b: { x: x1 + 1, y: y1 }};
// else
// perppts = { a: { x: x1 + 1, y: y1 }, b: { x: x1 - 1, y: y1 }};
// }
const { a, b } = perppts;
drawLine(a.x, a.y, b.x, b.y);
return walls.reduce((acc, w) => {
// console.log(w.corners);
const filtered = w.corners.filter(c => {
// https://stackoverflow.com/a/1560510
// position = sign((Bx - Ax) * (Y - Ay) - (By - Ay) * (X - Ax))
const det = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
return det < 0;
});
return [...acc, ...filtered];
}, []);
}
function updateTriangles([positionX, positionY]) {
const delim = ' ';
const className = 'clockwise-orientation';
if (!triangleContainer.childElementCount)
drawTriangles(triangleContainer, allEdgePts, position);
const triangles = triangleContainer.querySelectorAll('polygon');
triangles.forEach(t => {
const attr = t.getAttribute('points').split(delim);
const [a, b,] = attr.map(t => t.split(','));
const cw = isClockwise(a, b, [positionX, positionY]);
const acute = isAcute(a, b, [positionX, positionY]);
const pos = `${positionX},${positionY}`;
if (pos !== attr.pop()) {
attr.push(pos);
t.setAttribute('points', attr.join(delim));
}
t.classList[cw && acute ? "add" : "remove"](className);
t.classList[cw && !acute ? "add" : "remove"]("obtuse");
t.classList[!cw ? "add" : "remove"]("anti-clockwise");
});
}
function wrapPos(positionX, positionY) {
let x, y;
if (positionY > 150) y = positionY - 300;
else if (positionY < -150) y = positionY + 300;
else y = positionY;
if (positionX > 200) x = positionX - 400;
else if (positionX < -200) x = positionX + 400;
else x = positionX;
return [x, y];
}
function fireBullet(x, y, velocity) {
const speed = 200; // meters per second
const degrees = getRotate(gun);
const radians = degrees * Math.PI / 180; // toFixed(15)?
const vx = -Math.sin(radians);
const vy = Math.cos(radians);
const bulletTimeout = 5000; // miliseconds
const cannonLength = 8;
const el = document.createElementNS(namespaceURIsvg, 'circle');
el.classList.add('bullet');
el.setAttribute('r', 1);
el.setAttribute('cx', 0);
el.setAttribute('cy', 0);
const bullet = {
x: x + vx * cannonLength,
y: y + vy * cannonLength,
vx: vx * speed + velocity[0],
vy: vy * speed + velocity[1],
time: bulletTimeout,
node: bulletsContainer.appendChild(el)
}
bullets.push(bullet);
}
function getTranslate(el) {
let x, y;
if (el.style.transform.length === 0) {
x = 0;
y = 0;
} else {
[[, x], [, y] = ["0px", "0"]] = [...el.style.transform.matchAll(regex)];
}
return [+x, +y];
}
function getRotate(el) {
let [[, degrees] = ["0deg", "0"]] = [...el.style.transform.matchAll(degsRegex)];
return +degrees;
}
function updateBullets(elapsed) {
const deleteCount = 1;
[...bullets].forEach((bullet, index) => {
bullet.time -= elapsed;
const x = bullet.x + 0.001 * elapsed * bullet.vx;
const y = bullet.y + 0.001 * elapsed * bullet.vy;
bulletPt.x = x;
bulletPt.y = y;
if (bullet.time > 0 && ![...walls].some(w => w.isPointInFill(bulletPt))) {
[bullet.x, bullet.y] = wrapPos(x, y);
bullet.node.style.transform = `translate(${bullet.x}px, ${bullet.y}px)`;
} else {
bullet.node.remove();
bullets.splice(index, deleteCount);
}
});
}
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) {
const el = document.createElementNS(namespaceURIsvg, 'line');
el.setAttribute('x1', xa);
el.setAttribute('y1', ya);
el.setAttribute('x2', xb);
el.setAttribute('y2', yb);
svg.appendChild(el);
return el;
}
function slope({ xa, ya, xb, yb }) {
return (yb - ya) / (xb - xa);
}
function isLandable(edge) {
// console.log("edge", edge, "slope", slope(edge));
return Object.is(slope(edge), +0);
}
function detectEdgeCollision([xc, yc], { xa, ya, xb, yb }, shipRadius) {
const da = distance(xa, ya, xc, yc);
const db = distance(xb, yb, xc, yc);
// TODO: calculate this one ahead of time
const dc = distance(xa, ya, xb, yb);
// https://en.wikipedia.org/wiki/Altitude_(triangle)#Altitude_in_terms_of_the_sides
// Find altitude of side c (the base)
const s = (1 / 2) * (da + db + dc);
const hc = (2 / dc) * Math.sqrt(s * (s - da) * (s - db) * (s - dc));
return +hc.toFixed(2) <= shipRadius;
}
function detectCornerCollision([xc, yc], [x, y]) {
cornerPt.x = x - xc;
cornerPt.y = y - yc;
return shipBody.isPointInFill(cornerPt);
}
// function detectCollisions(position, walls, edges, z) {
function detectCollisions(position, walls, z) {
// console.log("detectCollision", position, walls, z);
let actualCorner;
const corner = walls.find(wall => {
const c = wall.find(corner => detectCornerCollision(position, corner));
actualCorner = c;
return c;
});
const edge = z.find(({ edge: pts, node: ee }) => {
const str = `${pts.xa},${pts.ya} ${pts.xb},${pts.yb}`;
const r = Object.is(slope(pts), 0) && s.gearDown ? shipRadius + 1 : shipRadius;
return detectEdgeCollision(position, pts, r);
});
// return edge || actualCorner;
return actualCorner;
}
function cornerCollisionPosition({ x: x1, y: y1 }, { x: x2, y: y2 }) {
// todo1: i only want the collisions ahead, not behind
// todo2: how do we tell if we've passed the collision point already?
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 }, node: n }) => {
const velNormIntxn = perpIntxn(slopev, x1, y1, x0, y0);
const d = Math.abs(diffy * x0 - diffx * y0 + detv) / dv;
const b = Math.sqrt(shipRadius ** 2 - d ** 2);
const cl = document.createElementNS(namespaceURIsvg, 'line');
cl.setAttribute('x1', velNormIntxn.x);
cl.setAttribute('y1', velNormIntxn.y);
cl.setAttribute('x2', x1);
cl.setAttribute('y2', y1);
return cl.getPointAtLength(b);
};
}
function collisionPosition(edge, position, velocity, radius) {
const baseSlope = slope(edge);
// if (Object.is(baseSlope, 0)) s.isLanded = true;
const { xa, ya, xb, yb } = edge;
const baseLine = { x1: xa, y1: ya, x2: xb, y2: yb };
const velocityLine = {
x1: position.x,
y1: position.y,
x2: position.x + velocity.x,
y2: position.y + velocity.y
};
// console.log("COLLISION DETECTED", s.collision);
// console.log("baseSlope", baseSlope);
const baseNrmlIntxn = perpIntxn(baseSlope, xa, ya, position.x, position.y);
const baseVelIntxn = lineIntxnPt(baseLine, velocityLine);
// console.log("baseNrmlIntxn", baseNrmlIntxn, "baseVelIntxn", baseVelIntxn);
const baseSegLength = distance(baseNrmlIntxn.x, baseNrmlIntxn.y , baseVelIntxn.x, baseVelIntxn.y);
const normalSegLength = distance(baseNrmlIntxn.x, baseNrmlIntxn.y, position.x, position.y);
// console.log("baseSegLength", baseSegLength, "normalSegLength", normalSegLength);
const theta = Math.atan(normalSegLength / baseSegLength);
const shipRadius = radius;
const h = shipRadius / Math.sin(theta);
const cl = document.createElementNS(namespaceURIsvg, 'line');
cl.setAttribute('x1', baseVelIntxn.x);
cl.setAttribute('y1', baseVelIntxn.y);
cl.setAttribute('x2', position.x);
cl.setAttribute('y2', position.y);
// console.log("h", h, "theta", theta);
const clPos = cl.getPointAtLength(h);
return clPos;
}
function lineIntxnPt({ x1, y1, x2, y2 }, { x1: x3, y1: y3, x2: x4, y2: y4 }) {
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
const denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
const l1Det = x1 * y2 - y1 * x2;
const l2Det = x3 * y4 - y3 * x4;
const x = (l1Det * (x3 - x4) - (x1 - x2) * l2Det) / denominator;
const y = (l1Det * (y3 - y4) - (y1 - y2) * l2Det) / denominator;
return { x: x, y: y };
}
function updateShip(s, elapsed, edgeszz) {
const degrees = getRotate(gun);
if (rotate > 0) gun.style.transform = `rotate(${(+degrees + rotationSpeed * elapsed) % 360}deg)`;
else if (rotate < 0) gun.style.transform = `rotate(${(+degrees - rotationSpeed * elapsed) % 360}deg)`;
let { x: velocityX, y: velocityY } = s.velocity;
let { x: accelerationX, y: accelerationY } = s.acceleration;
velocityX = velocityX > 0 && velocityX + accelerationX < 0 ? 0 : velocityX + accelerationX;
velocityY = velocityY > 0 && velocityY + accelerationY < 0 ? 0 : velocityY + accelerationY;
s.velocity = { x: velocityX, y: velocityY };
velIndic.setAttribute('x2', s.velocity.x);
velIndic.setAttribute('y2', s.velocity.y);
acclIndic.setAttribute('x2', s.acceleration.x);
acclIndic.setAttribute('y2', s.acceleration.y);
const changeX = 0.001 * elapsed * velocityX;
const changeY = 0.001 * elapsed * velocityY;
let { x, y } = s.position;
// console.log("current position", x, y);
// console.log("elapsed", elapsed, "changeX", changeX, "changeY", changeY);
// let position = [positionX, positionY] = restart ? [0, 0] : wrapPos(changeX + x, changeY + y);
let position = [positionX, positionY] = [changeX + x, changeY + y];
let [xc, yc] = position;
// console.log("future position", xc, yc);
const collE = getCollisionEdges(edgeszz, position);
// console.log("position", { x, y }, "velocity", s.velocity);
const collC = getCollisionCorners(ws, { x, y }, s.velocity);
const fCollC = getForwardCollisionCorners(ws, { x, y }, s.velocity);
// console.log("collision corners", collC);
console.log("forward collision corners", fCollC);
ccps = collC.map(cornerCollisionPosition({ x, y }, s.velocity));
// console.log("corner collision position", ccps);
current = s.collision;
s.collision = detectCollisions(position, allWallCorners, collE);
// legs.style.display = !legs.style.display || legs.style.display === "none" ? "initial" : "none";
legs.style.display = s.gearDown ? "initial" : "none";
if (!current && s.collision) {
const baseSlope = slope(s.collision.edge);
if (Object.is(baseSlope, 0) && s.gearDown) s.isLanded = true;
const clPos = collisionPosition(s.collision.edge, s.position, s.velocity, s.gearDown && Object.is(baseSlope, 0) ? shipRadius + 1 : shipRadius);
s.velocity = { x: 0, y: 0 };
s.position = { x: clPos.x, y: clPos.y }
s.node.style.transform = `translate(${s.position.x}px, ${s.position.y}px)`;
} else if (current && s.collision) {
s.velocity = { x: 0, y: 0 };
} else {
if (s.isLanded) {
s.gearDown = false;
s.isLanded = false;
}
s.position = { x: positionX, y: positionY }
s.node.style.transform = `translate(${positionX}px, ${positionY}px)`;
}
}
function updateEdges(position) {
const collisionEdges = findAllEdges(allEdgePts, position);
[...edgeContainer.children].forEach(l => {
const x1 = l.getAttribute('x1');
const y1 = l.getAttribute('y1');
const x2 = l.getAttribute('x2');
const y2 = l.getAttribute('y2');
const edge = `${x1},${y1} ${x2},${y2}`;
if (collisionEdges.includes(edge))
if ([
edgeContainer.childElementCount <= allStartingEdges.length,
!allStartingEdges.includes(edge)
].some(c => c))
l.remove();
});
}
function updateLines(elapsed, walls, position, velocity) {
const edges = walls.reduce((acc, wall) => {
return [...acc, ...wall.edges];
}, []);
const edgeIds = edges.map(e => `normal${e.xa}-${e.ya}-${e.xb}-${e.yb}`);
const nodes = [...linesContainer.children];
nodes.forEach(n => {
if (!edgeIds.includes(n.id)) n.remove();
});
// console.log("EDGES", edges);
edges.forEach(({ xa, ya, xb, yb }) => {
const id = `normal${xa}-${ya}-${xb}-${yb}`;
const g = linesContainer.querySelector(`#${id}`) || document.createElementNS(namespaceURIsvg, 'g');
const el = g.querySelector('line') || document.createElementNS(namespaceURIsvg, 'line');
const star = g.querySelector('circle') || document.createElementNS(namespaceURIsvg, 'circle');
star.setAttribute('r', 1);
// console.log(position, velocity, xa, ya, xb, yb);
const baseSlope = slope({ xa, ya, xb, yb });
let isx, isy;
// console.log("BASESLOPE", baseSlope);
if (baseSlope === -Infinity || baseSlope === Infinity) {
isx = xa;
isy = position.y;
} else if (baseSlope === 0) { // base is horizontal
isx = position.x;
isy = ya;
} else {
// const clPt = collisionPosition({ xa, ya, xb, yb }, position, velocity);
// console.log(clPt);
// isx = clPt.x;
// isy = clPt.y;
star.setAttribute('cx', 0);
star.setAttribute('cy', 0);
}
return g;
});
}
function init(edgePts) {
started = false;
position = [0, 0]; // meters
velocity = [0, 0]; // meters per second
acceleration = [0, 0]; // meters per second per second
rotate = 0;
[...edgeContainer.children].forEach(c => c.remove());;
// drawAllEdges(edgePts);
allStartingEdges = findAllEdges(edgePts, position);
ship.style.transform = "";
walls.forEach(w => w.setAttribute('fill', 'black'));
// bg.style.fill = 'black';
time.innerText = "0";
}
function firstFrame(timestamp) {
zero = timestamp;
zeroForTimer = timestamp;
previous = timestamp;
animate(timestamp);
}
function animate(timestamp) {
// console.log("current timestamp", timestamp, "previous", previous);
const elapsed = timestamp - previous;
const delta = timestamp - zero;
let degrees = getRotate(gun);
previous = timestamp;
if (delta >= 1000) {
fps.innerText = frameCount;
// debug.innerText = `velocity ${velocity}\n`
// + 'bullets\nx\ty\tvx\tvy\n'
// + bullets.map(b => {
// return `${b.x.toFixed(2)}\t${b.y.toFixed(2)}\t${b.vx.toFixed(2)}\t${b.vy.toFixed(2)}`;
// }).join("\n");
zero = timestamp;
frameCount = 0;
} else {
frameCount++;
}
// console.log("COLLISION EDGES", edgeszz);
updateShip(s, elapsed, edgeszz);
updateBullets(elapsed);
// updateEdges(position);
// if (!s.collision) updateLines(elapsed, edgeszz, s.position, s.velocity);
if (drawCollisionLines) updateTriangles(position);
if (s.collision && !s.isLanded) {
started = false;
isReadingKeys = false;
walls.forEach(w => w.setAttribute('fill', 'red'));
// bg.style.fill = 'red';
}
if (restart) {
started = false;
restart = false;
init(allEdgePts);
time.innerText = 0;
}
// const finished = edgeContainer.childElementCount <= 0;
// if (finished) started = false;
if (started) {
time.innerText = ((timestamp - zeroForTimer) * 0.001).toFixed(3);
// requestAnimationFrame(t => animate(t));
requestAnimationFrame(animate);
}
}
let force = 1;
let spacePressed = false;
let restartPressed = false;
let upPressed = false;
let downPressed = false;
let leftPressed = false;
let rightPressed = false;
let rotateCWPressed = false;
let rotateCCWPressed = false;
document.addEventListener("keydown", function(e) {
if (!isReadingKeys) return;
if (!started) {
started = true;
frameCount = 0;
requestAnimationFrame(firstFrame);
}
switch (e.code) {
case "Space":
if (!spacePressed) {
spacePressed = true;
const [x, y] = getTranslate(ship);
fireBullet(x, y, velocity);
}
break;
case "KeyW":
case "ArrowUp":
if (!upPressed) {
upPressed = true;
s.acceleration.y += -force;
}
break;
case "KeyS":
case "ArrowDown":
if (!downPressed) {
downPressed = true;
s.acceleration.y += force;
}
break;
case "KeyA":
case "ArrowLeft":
if (!leftPressed) {
leftPressed = true;
if (!s.gearDown) s.acceleration.x += -force;
}
break;
case "KeyD":
case "ArrowRight":
if (!rightPressed) {
rightPressed = true;
if (!s.gearDown) s.acceleration.x += force;
}
break;
case "KeyQ":
case "Comma":
if (!rotateCCWPressed) {
rotateCCWPressed = true;
rotate += -1;
}
break;
case "KeyE":
case "Period":
if (!rotateCWPressed) {
rotateCWPressed = true;
rotate += 1;
}
break;
case "KeyP": // Pause
started = !started;
break;
case "KeyG": // Landing gear
s.gearDown = !s.gearDown;
break;
}
});
document.addEventListener("keyup", function(e) {
switch (e.code) {
case "Space":
spacePressed = false;
break;
case "KeyR":
isReadingKeys = true;
!started ? init(allEdgePts) : restart = true;
break;
case "KeyW":
case "ArrowUp":
if (upPressed) {
upPressed = false;
s.acceleration.y -= -force;
}
break;
case "KeyS":
case "ArrowDown":
if (downPressed) {
downPressed = false;
s.acceleration.y -= force;
}
break;
case "KeyA":
case "ArrowLeft":
if (leftPressed) {
leftPressed = false;
if (!s.gearDown) s.acceleration.x -= -force;
}
break;
case "KeyD":
case "ArrowRight":
if (rightPressed) {
rightPressed = false;
if (!s.gearDown) s.acceleration.x -= force;
}
break;
case "KeyQ":
case "Comma":
if (rotateCCWPressed) {
rotateCCWPressed = false;
rotate -= -1;
}
break;
case "KeyE":
case "Period":
if (rotateCWPressed) {
rotateCWPressed = false;
rotate -= 1;
}
break;
}
});
const pointer = svg.querySelector('#pointer');
const xp = pointer.querySelector('.x');
const yp = pointer.querySelector('.y');
const pointerPt = svg.createSVGPoint();
svg.addEventListener("pointermove", function({ clientX, clientY }) {
pointerPt.x = clientX;
pointerPt.y = clientY;
// https://www.sitepoint.com/how-to-translate-from-dom-to-svg-coordinates-and-back-again/
const svgP = pointerPt.matrixTransform(svg.getScreenCTM().inverse());
if (bg.isPointInFill(svgP)) {
// console.log(Math.trunc(svgP.x), Math.trunc(svgP.y));
xp.innerText = Math.trunc(svgP.x);
yp.innerText = Math.trunc(svgP.y);
}
});
//]]></script>
</svg>