303 lines
9.5 KiB
HTML
303 lines
9.5 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Wireframe Flight Background</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
overflow: hidden;
|
|
background: #000;
|
|
}
|
|
|
|
canvas {
|
|
display: block;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="bgCanvas"></canvas>
|
|
<script>
|
|
const canvas = document.getElementById('bgCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
let width, height;
|
|
|
|
function resize() {
|
|
width = canvas.width = window.innerWidth;
|
|
height = canvas.height = window.innerHeight;
|
|
}
|
|
|
|
window.addEventListener('resize', resize);
|
|
resize();
|
|
|
|
const camera = {
|
|
fov: 800,
|
|
angle: Math.PI / 4
|
|
};
|
|
|
|
class Point3D {
|
|
constructor(x, y, z) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.z = z;
|
|
}
|
|
|
|
project() {
|
|
const scale = camera.fov / (camera.fov + this.z);
|
|
const xIso = this.x - this.z;
|
|
const yIso = (this.x + this.z) * 0.5 - this.y;
|
|
const x2d = xIso * scale + width / 2;
|
|
const y2d = yIso * scale + height / 3;
|
|
return { x: x2d, y: y2d, scale: scale };
|
|
}
|
|
}
|
|
|
|
class Triangle {
|
|
constructor(p1, p2, p3) {
|
|
this.p1 = p1;
|
|
this.p2 = p2;
|
|
this.p3 = p3;
|
|
this.calculateNormal();
|
|
this.visible = true;
|
|
}
|
|
|
|
calculateNormal() {
|
|
const v1 = { x: this.p2.x - this.p1.x, y: this.p2.y - this.p1.y, z: this.p2.z - this.p1.z };
|
|
const v2 = { x: this.p3.x - this.p1.x, y: this.p3.y - this.p1.y, z: this.p3.z - this.p1.z };
|
|
this.normal = {
|
|
x: v1.y * v2.z - v1.z * v2.y,
|
|
y: v1.z * v2.x - v1.x * v2.z,
|
|
z: v1.x * v2.y - v1.y * v2.x
|
|
};
|
|
const len = Math.sqrt(this.normal.x**2 + this.normal.y**2 + this.normal.z**2);
|
|
if (len > 0) {
|
|
this.normal.x /= len;
|
|
this.normal.y /= len;
|
|
this.normal.z /= len;
|
|
}
|
|
const light = { x: 0.5, y: 0.5, z: 1 };
|
|
const dot = this.normal.x * light.x + this.normal.y * light.y + this.normal.z * light.z;
|
|
this.intensity = Math.max(0, dot);
|
|
}
|
|
|
|
isBackfaceCulled() {
|
|
const v1 = { x: this.p2.x - this.p1.x, y: this.p2.y - this.p1.y, z: this.p2.z - this.p1.z };
|
|
const v2 = { x: this.p3.x - this.p1.x, y: this.p3.y - this.p1.y, z: this.p3.z - this.p1.z };
|
|
const crossZ = v1.x * v2.y - v1.y * v2.x;
|
|
return crossZ >= 0;
|
|
}
|
|
|
|
drawFilled() {
|
|
const proj1 = this.p1.project();
|
|
const proj2 = this.p2.project();
|
|
const proj3 = this.p3.project();
|
|
|
|
if (this.isBackfaceCulled()) return;
|
|
|
|
const colorVal = Math.floor(30 + this.intensity * 150);
|
|
const color = `rgba(0, ${colorVal}, 0, 0.8)`;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(proj1.x, proj1.y);
|
|
ctx.lineTo(proj2.x, proj2.y);
|
|
ctx.lineTo(proj3.x, proj3.y);
|
|
ctx.closePath();
|
|
ctx.fillStyle = color;
|
|
ctx.fill();
|
|
}
|
|
|
|
drawWireframe() {
|
|
const proj1 = this.p1.project();
|
|
const proj2 = this.p2.project();
|
|
const proj3 = this.p3.project();
|
|
|
|
if (this.isBackfaceCulled()) return;
|
|
|
|
const color = this.p1.isWater || this.p2.isWater || this.p3.isWater ? '#00ccff' : '#00ff00';
|
|
|
|
ctx.strokeStyle = color;
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath();
|
|
ctx.moveTo(proj1.x, proj1.y);
|
|
ctx.lineTo(proj2.x, proj2.y);
|
|
ctx.lineTo(proj3.x, proj3.y);
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
class Landscape {
|
|
constructor() {
|
|
this.gridSize = 60;
|
|
this.gridRange = 40;
|
|
this.offsetX = 0;
|
|
this.offsetZ = 0;
|
|
this.triangles = [];
|
|
this.waterLevel = -10;
|
|
}
|
|
|
|
generateHeight(x, z) {
|
|
const nx = x / 100;
|
|
const nz = z / 100;
|
|
let y = 0;
|
|
y += Math.sin(nx) * 30;
|
|
y += Math.cos(nz) * 30;
|
|
y += Math.sin(nx * 2 + nz * 0.5) * 15;
|
|
y += Math.cos(nz * 2 - nx) * 15;
|
|
return y;
|
|
}
|
|
|
|
isWater(x, z, y) {
|
|
return y < this.waterLevel;
|
|
}
|
|
|
|
update() {
|
|
this.offsetX -= 1;
|
|
this.offsetZ -= 1;
|
|
this.generateTriangles();
|
|
}
|
|
|
|
generateTriangles() {
|
|
this.triangles = [];
|
|
const minIdx = -this.gridRange;
|
|
const maxIdx = this.gridRange;
|
|
|
|
for (let x = minIdx; x <= maxIdx; x++) {
|
|
for (let z = minIdx; z <= maxIdx; z++) {
|
|
const p1 = this.getPoint(x, z);
|
|
const p2 = this.getPoint(x + 1, z);
|
|
const p3 = this.getPoint(x + 1, z + 1);
|
|
const p4 = this.getPoint(x, z + 1);
|
|
|
|
const tri1 = new Triangle(p1, p2, p3);
|
|
const tri2 = new Triangle(p1, p3, p4);
|
|
|
|
this.triangles.push(tri1);
|
|
this.triangles.push(tri2);
|
|
}
|
|
}
|
|
}
|
|
|
|
getPoint(x, z) {
|
|
const worldX = x * this.gridSize + this.offsetX;
|
|
const worldZ = z * this.gridSize + this.offsetZ;
|
|
const y = this.generateHeight(worldX, worldZ);
|
|
const point = new Point3D(worldX, y, worldZ);
|
|
point.isWater = this.isWater(worldX, worldZ, y);
|
|
return point;
|
|
}
|
|
|
|
draw() {
|
|
this.triangles.forEach(tri => tri.drawFilled());
|
|
this.triangles.forEach(tri => tri.drawWireframe());
|
|
}
|
|
}
|
|
|
|
class Airplane {
|
|
constructor() {
|
|
this.z = 100;
|
|
this.yaw = 0;
|
|
this.roll = 0;
|
|
this.pitch = 0;
|
|
this.wireframe = [];
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
const w = 15, h = 5, d = 25;
|
|
const vertices = [
|
|
{ x: 0, y: -h, z: 0 },
|
|
{ x: w, y: 0, z: d / 2 },
|
|
{ x: -w, y: 0, z: d / 2 },
|
|
{ x: 0, y: 0, z: -d / 2 },
|
|
{ x: w, y: 0, z: -d / 4 },
|
|
{ x: -w, y: 0, z: -d / 4 },
|
|
{ x: 0, y: h, z: 0 }
|
|
];
|
|
|
|
const edges = [
|
|
[0, 1], [0, 2], [0, 6],
|
|
[1, 2],
|
|
[1, 4], [2, 5],
|
|
[3, 4], [3, 5],
|
|
[4, 5],
|
|
[6, 1], [6, 2], [6, 3]
|
|
];
|
|
|
|
this.wireframe = { vertices, edges };
|
|
}
|
|
|
|
update() {
|
|
this.yaw = Math.sin(Date.now() / 1000) * 0.2;
|
|
this.roll = Math.sin(Date.now() / 600) * 0.4;
|
|
this.pitch = Math.cos(Date.now() / 800) * 0.25;
|
|
}
|
|
|
|
draw() {
|
|
ctx.strokeStyle = '#ff0000';
|
|
ctx.lineWidth = 2;
|
|
ctx.shadowBlur = 15;
|
|
ctx.shadowColor = '#ff0000';
|
|
|
|
const rotated = this.wireframe.vertices.map(v => {
|
|
let x = v.x, y = v.y, z = v.z;
|
|
|
|
const cosY = Math.cos(this.yaw), sinY = Math.sin(this.yaw);
|
|
const x1 = x * cosY - z * sinY;
|
|
const z1 = x * sinY + z * cosY;
|
|
|
|
const cosR = Math.cos(this.roll), sinR = Math.sin(this.roll);
|
|
const x2 = x1 * cosR - y * sinR;
|
|
const y1 = x1 * sinR + y * cosR;
|
|
|
|
const cosP = Math.cos(this.pitch), sinP = Math.sin(this.pitch);
|
|
const y2 = y1 * cosP - z1 * sinP;
|
|
const z2 = y1 * sinP + z1 * cosP;
|
|
|
|
return new Point3D(x2, y2, this.z + z2);
|
|
});
|
|
|
|
ctx.beginPath();
|
|
this.wireframe.edges.forEach(([a, b]) => {
|
|
const p1 = rotated[a].project();
|
|
const p2 = rotated[b].project();
|
|
ctx.moveTo(p1.x, p1.y);
|
|
ctx.lineTo(p2.x, p2.y);
|
|
});
|
|
ctx.stroke();
|
|
|
|
ctx.shadowBlur = 0;
|
|
}
|
|
}
|
|
|
|
const landscape = new Landscape();
|
|
const airplane = new Airplane();
|
|
|
|
function animate() {
|
|
ctx.fillStyle = '#000';
|
|
ctx.fillRect(0, 0, width, height);
|
|
|
|
landscape.update();
|
|
landscape.draw();
|
|
|
|
airplane.update();
|
|
airplane.draw();
|
|
|
|
requestAnimationFrame(animate);
|
|
}
|
|
|
|
animate();
|
|
</script>
|
|
</body>
|
|
</html>
|