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

620 lines
19 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">
<style>
#frames {
position: absolute;
right: 0px;
}
p {
border: 1px solid black;
box-size: border-box;
}
rect#bg {
fill: gray;
}
.ship rect {
fill: green;
}
.ship circle {
fill: white;
}
#crosshair {
opacity: 0.5;
}
circle.bullet {
fill: yellow;
}
#frames {
padding: 1px;
font-size: 4pt;
font-family: courier;
}
#lines {
opacity: 0.5;
}
#lines line {
stroke: limegreen;
stroke-width: 0.5px;
}
#lines polygon {
fill-opacity: 0.2;
stroke-width: 0.5px;
fill: none;
stroke: none;
}
#lines polygon.clockwise-orientation {
fill: white;
stroke: red;
}
#legs {
display: none;
}
</style>
<rect id="bg" x="-200" y="-150" width="400" height="300"/>
<g>
<g class="hitbox">
<g class="ship">
<line id="cannon" x1="0" y1="0" x2="0" y2="8" stroke="black"/>
<circle id="body" cx="0" cy="0" r="5"/>
<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>
</g>
<polygon id="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" />
<!-- <rect id="rect1" x="30" y="30" width="20" height="20"/> -->
<g id="lines">
</g>
<g id="bullets"></g>
</g>
<foreignObject x="-200" y="-150" width="100%" height="100%">
<div id="frames" 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">0</span> fps
</div>
<!-- <button id="turn-left" xmlns="http://www.w3.org/1999/xhtml">🡐</button> -->
<!-- <button id="move-backward" xmlns="http://www.w3.org/1999/xhtml">🡑</button> -->
<!-- <button id="move-forward" xmlns="http://www.w3.org/1999/xhtml">🡓</button> -->
<!-- <button id="turn-right" xmlns="http://www.w3.org/1999/xhtml">🡒</button> -->
<!-- <button id="rotate-ccw" xmlns="http://www.w3.org/1999/xhtml">⟲</button> -->
<!-- <button id="rotate-cw" xmlns="http://www.w3.org/1999/xhtml">⟳</button> -->
<!-- <button id="fire" xmlns="http://www.w3.org/1999/xhtml">Fire</button> -->
<pre id="debug" xmlns="http://www.w3.org/1999/xhtml"></pre>
</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 = [];
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 friction = 7.5;
let friction = 0;
let rotate = 0;
let rotationSpeed = 0.25;
const maxSpeed = 100;
const fps = document.querySelector("#fps");
const time = document.querySelector("#time");
const info = document.querySelector("#debug");
const ship = document.querySelector(".ship");
const gun = ship.querySelector('#cannon');
const shipBody = ship.querySelector("circle");
const hitbox = document.querySelector(".hitbox");
const bulletsContainer = document.querySelector("#bullets");
const leftTurnButton = document.querySelector("#turn-left");
const rightTurnButton = document.querySelector("#turn-right");
const reverseMoveButton = document.querySelector("#move-backward");
const forwardMoveButton = document.querySelector("#move-forward");
const rotateCWButton = document.querySelector("#rotate-cw");
const rotateCCWButton = document.querySelector("#rotate-ccw");
const fireButton = document.querySelector("#fire");
const pt = document.querySelector('svg').createSVGPoint();
const cornerPt = document.querySelector('svg').createSVGPoint();
const wall = document.querySelector('#wall');
const points = wall.getAttribute('points').split(' ').map(coords => {
const [x, y] = coords.split(',');
return [+x, +y];
});
const triangleContainer = document.querySelector('#lines');
const trianglePts = points.map((pt, i) => [pt, points[(i + 1) % points.length]]);
function drawTriangles(container, pts, [positionX, positionY]) {
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)
});
}
drawTriangles(triangleContainer, trianglePts, [0, 0]);
const triangles = triangleContainer.querySelectorAll('polygon');
function updateTriangles([positionX, positionY]) {
const delim = ' ';
const className = 'clockwise-orientation';
const visible = [];
const PI = Math.PI / 2;
triangles.forEach(t => {
const attr = t.getAttribute('points').split(delim);
const [[xa, ya], [xb, yb], [xc, yc]] = attr.map(t => t.split(','));
// https://en.wikipedia.org/wiki/Curve_orientation#Practical_considerations
// Determinant for a convex polygon
const det = (+xb - +xa) * (+yc - +ya) - (+xc - +xa) * (+yb - +ya);
const pos = `${positionX},${positionY}`;
const cwOrientation = det < 0;
const isClockwise = det < 0;
let isAcute = false;
if (isClockwise) {
const [[ax, ay], [bx, by], [shipx, shipy]] =
t.getAttribute('points').split(' ').map(n => n.split(',').map(n => +n));
const da = distance(ax, ay, shipx, shipy);
const db = distance(bx, by, shipx, shipy);
const dc = distance(ax, ay, bx, by);
// https://en.wikipedia.org/wiki/Law_of_cosines
// Solve for angles α and β with inverse cosine (arccosine)
const α = Math.acos((db ** 2 + dc ** 2 - da ** 2) / (2 * db * dc));
const β = Math.acos((da ** 2 + dc ** 2 - db ** 2) / (2 * da * dc));
isAcute = α < PI && β < PI;
}
if (pos !== attr.pop()) {
attr.push(pos);
t.setAttribute('points', attr.join(delim));
}
t.classList[isClockwise && isAcute ? "add" : "remove"](className);
if (isClockwise && isAcute) visible.push(t);
});
return visible;
}
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 degrees = getRotate(gun);
const radians = degrees * Math.PI / 180; // toFixed(15)?
const speed = 200; // meters per second
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;
const pt = document.querySelector('svg').createSVGPoint();
[...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;
pt.x = x;
pt.y = y;
if (bullet.time > 0 && !wall.isPointInFill(pt)) {
[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 updateLines([positionX, positionY]) {
lines.forEach(line => {
line.setAttribute('x2', positionX);
line.setAttribute('y2', positionY);
// let slope = (+line.getAttribute('y2') - +line.getAttribute('y1')) / (+line.getAttribute('x2') - +line.getAttribute('x1'));
// slope = +slope.toFixed(15);
// console.log('slope', slope);
const firstP = line.getPointAtLength(1);
if (polygon.isPointInFill(firstP)) {
line.setAttribute('x2', line.getAttribute('x1'));
line.setAttribute('y2', line.getAttribute('y1'));
}
});
}
requestAnimationFrame(firstFrame);
let start;
let restart = false;
function firstFrame(timestamp) {
zero = timestamp;
zeroForTimer = timestamp;
previous = timestamp;
animate(timestamp);
}
function distance(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
function animate(timestamp) {
const delta = timestamp - zero;
time.innerText = ((timestamp - zeroForTimer) * 0.001).toFixed(3);
// time.innerText = time.inner
const elapsed = timestamp - previous;
previous = timestamp;
if (delta >= 1000) {
fps.innerText = frameCount;
// info.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++;
}
let 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 [velocityX, velocityY] = restart ? [0, 0] : velocity;
let [accelerationX, accelerationY] = restart ? [0, 0] : acceleration;
if (velocityX > 0) accelerationX += -friction;
else if (velocityX < 0) accelerationX += friction;
if (velocityY > 0) accelerationY += -friction;
else if (velocityY < 0) accelerationY += friction;
velocityX = velocityX > 0 && velocityX + accelerationX < 0 ? 0 : velocityX + accelerationX;
velocityY = velocityY > 0 && velocityY + accelerationY < 0 ? 0 : velocityY + accelerationY;
velocity = [velocityX, velocityY];
if (velocity[0] > maxSpeed) velocity[0] = maxSpeed;
else if (velocity[0] < -maxSpeed) velocity[0] = -maxSpeed
if (velocity[1] > maxSpeed) velocity[1] = maxSpeed;
else if (velocity[1] < -maxSpeed) velocity[1] = -maxSpeed
const changeX = 0.001 * elapsed * velocityX;
const changeY = 0.001 * elapsed * velocityY;
let [x, y] = getTranslate(hitbox);
let position = [positionX, positionY] = restart ? [0, 0] : wrapPos(changeX + x, changeY + y);
if (restart) restart = false;
updateBullets(elapsed);
// updateLines(position);
const visibleTriangles = updateTriangles(position);
// info.innerText = [...visibleTriangles].map(t => {
// const [[ax, ay], [bx, by], [shipx, shipy]] =
// t.getAttribute('points').split(' ').map(n => n.split(',').map(n => +n));
//
// const da = distance(ax, ay, shipx, shipy);
// const db = distance(bx, by, shipx, shipy);
// const dc = distance(ax, ay, bx, by);
// const s = (1 / 2) * (da + db + dc);
// const hc = (2 / dc) * Math.sqrt(s * (s - da) * (s - db) * (s - dc));
//
// 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));
//
// const PI = Math.PI / 2;
// const acute = alpha < PI && beta < PI;
//
// return `${da.toFixed(2)} ${db.toFixed(2)} ${dc.toFixed(2)} ${hc.toFixed(2)} ${alpha.toFixed(2)} ${beta.toFixed(2)}`;
// }).join('\n');
// can do this after side collision detection
// const ts = updateTriangles(position);
// const corners = ts.reduce((acc, t) => {
// const [a, b,] = t.getAttribute('points').split(' ');
// // return p.map(t => t.split(','));
// // acc.push(a);
// // acc.push(b);
// return [a, b, ...acc];
// }, []);
//
// const uniqueCorners = [...new Set(corners)].map(n => n.split(',').map(n => +n));
const cornerCollision = points.some(([x, y]) => {
cornerPt.x = x - positionX;
cornerPt.y = y - positionY;
return shipBody.isPointInFill(cornerPt);
});
const PI = Math.PI / 2;
const shipRadius = 5;
const sideCollision = [...visibleTriangles].reduce((acc, t) => {
const [[ax, ay], [bx, by], [shipx, shipy]] =
t.getAttribute('points').split(' ').map(n => n.split(',').map(n => +n));
const da = distance(ax, ay, shipx, shipy);
const db = distance(bx, by, shipx, shipy);
const dc = distance(ax, ay, bx, by);
const s = (1 / 2) * (da + db + dc);
const hc = (2 / dc) * Math.sqrt(s * (s - da) * (s - db) * (s - dc));
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));
const acute = alpha < PI && beta < PI;
return acute ? [...acc, hc] : acc;
}, []).some(h => h <= shipRadius);
wall.setAttribute('fill', cornerCollision || sideCollision ? 'red' : 'black');
hitbox.style.transform = `translate(${positionX}px, ${positionY}px)`;
if (cornerCollision || sideCollision) {
// restart game
zeroForTimer = timestamp;
restart = true;
// position = [0, 0];
// acceleration = [0, 0];
}
// if (+y < 200)
// if (timestamp < 10000)
requestAnimationFrame(t => animate(t));
}
let force = 1;
let spacePressed = false;
let upPressed = false;
let downPressed = false;
let leftPressed = false;
let rightPressed = false;
let rotateCWPressed = false;
let rotateCCWPressed = false;
document.addEventListener("keydown", function(e) {
switch (e.code) {
case "Space":
if (!spacePressed) {
spacePressed = true;
const [x, y] = getTranslate(hitbox);
fireBullet(x, y, velocity);
}
break;
case "KeyW":
case "ArrowUp":
if (!upPressed) {
upPressed = true;
acceleration[1] += -force;
}
break;
case "KeyS":
case "ArrowDown":
if (!downPressed) {
downPressed = true;
acceleration[1] += force;
}
break;
case "KeyA":
case "ArrowLeft":
if (!leftPressed) {
leftPressed = true;
acceleration[0] += -force;
}
break;
case "KeyD":
case "ArrowRight":
if (!rightPressed) {
rightPressed = true;
acceleration[0] += force;
}
break;
case "KeyQ":
case "Comma":
if (!rotateCCWPressed) {
rotateCCWPressed = true;
rotate += -1;
}
break;
case "KeyE":
case "Period":
if (!rotateCWPressed) {
rotateCWPressed = true;
rotate += 1;
}
break;
}
});
document.addEventListener("keyup", function(e) {
switch (e.code) {
case "Space":
spacePressed = false;
break;
case "KeyW":
case "ArrowUp":
if (upPressed) {
upPressed = false;
acceleration[1] -= -force;
}
break;
case "KeyS":
case "ArrowDown":
if (downPressed) {
downPressed = false;
acceleration[1] -= force;
}
break;
case "KeyA":
case "ArrowLeft":
if (leftPressed) {
leftPressed = false;
acceleration[0] -= -force;
}
break;
case "KeyD":
case "ArrowRight":
if (rightPressed) {
rightPressed = false;
acceleration[0] -= force;
}
break;
case "KeyQ":
case "Comma":
if (rotateCCWPressed) {
rotateCCWPressed = false;
rotate -= -1;
}
break;
case "KeyE":
case "Period":
if (rotateCWPressed) {
rotateCWPressed = false;
rotate -= 1;
}
break;
}
});
// leftTurnButton.addEventListener("mousedown", function (e) {
// acceleration[0] = -force;
// });
//
// leftTurnButton.addEventListener("mouseup", function (e) {
// acceleration[0] = 0;
// });
//
// rightTurnButton.addEventListener("mousedown", function (e) {
// acceleration[0] = force;
// });
//
// rightTurnButton.addEventListener("mouseup", function (e) {
// acceleration[0] = 0;
// });
//
// reverseMoveButton.addEventListener("mousedown", function (e) {
// acceleration[1] = -force;
// });
//
// reverseMoveButton.addEventListener("mouseup", function (e) {
// acceleration[1] = 0;
// });
//
// forwardMoveButton.addEventListener("mousedown", function (e) {
// acceleration[1] = force;
// });
//
// forwardMoveButton.addEventListener("mouseup", function (e) {
// acceleration[1] = 0;
// });
//
// rotateCWButton.addEventListener("mousedown", function (e) {
// rotate = 1;
// });
//
// rotateCWButton.addEventListener("mouseup", function (e) {
// rotate = 0;
// });
//
// rotateCCWButton.addEventListener("mousedown", function (e) {
// rotate = -1;
// });
//
// rotateCCWButton.addEventListener("mouseup", function (e) {
// rotate = 0;
// });
//
// fireButton.addEventListener("click", function (e) {
// const [x, y] = getTranslate(hitbox);
// fireBullet(x, y, velocity);
// });
//]]></script>
</svg>