Update animation: full screen, centered airplane, procedural landscape with water
This commit is contained in:
1 parent
5c52fb48f6
commit
5cec45cd4a
1 file changed
+224
-1
+224
-1
@@ -23,6 +23,229 @@
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="bgCanvas"></canvas>
|
||||
<script src="script.js"></script>
|
||||
<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: 600,
|
||||
zoom: 1
|
||||
};
|
||||
|
||||
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 x2d = this.x * scale + width / 2;
|
||||
const y2d = this.y * scale + height / 2;
|
||||
return { x: x2d, y: y2d, scale: scale };
|
||||
}
|
||||
}
|
||||
|
||||
class Landscape {
|
||||
constructor() {
|
||||
this.gridSize = 60;
|
||||
this.gridRange = 30;
|
||||
this.offsetZ = 0;
|
||||
this.points = new Map();
|
||||
this.waterLevel = -15;
|
||||
this.init();
|
||||
}
|
||||
|
||||
getKey(x, z) {
|
||||
return `${Math.round(x / this.gridSize)},${Math.round(z / this.gridSize)}`;
|
||||
}
|
||||
|
||||
generateHeight(x, z) {
|
||||
const nx = x / 80;
|
||||
const nz = z / 80;
|
||||
let y = 0;
|
||||
y += Math.sin(nx) * 25;
|
||||
y += Math.cos(nz) * 25;
|
||||
y += Math.sin(nx * 2 + nz * 0.5) * 15;
|
||||
y += Math.cos(nz * 2 - nx) * 15;
|
||||
y += Math.sin(nx * 3 - nz * 2) * 8;
|
||||
return y;
|
||||
}
|
||||
|
||||
isWater(x, z, y) {
|
||||
return y < this.waterLevel;
|
||||
}
|
||||
|
||||
update() {
|
||||
this.offsetZ -= 0.5;
|
||||
if (this.offsetZ < -this.gridSize) {
|
||||
this.offsetZ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
getPoint(x, z) {
|
||||
const key = this.getKey(x, z + this.offsetZ);
|
||||
if (this.points.has(key)) {
|
||||
return this.points.get(key);
|
||||
}
|
||||
|
||||
const worldX = x * this.gridSize;
|
||||
const worldZ = (z + this.offsetZ / this.gridSize) * this.gridSize;
|
||||
const y = this.generateHeight(worldX, worldZ);
|
||||
const point = new Point3D(worldX, y, worldZ);
|
||||
point.isWater = this.isWater(worldX, worldZ, y);
|
||||
this.points.set(key, point);
|
||||
return point;
|
||||
}
|
||||
|
||||
draw() {
|
||||
ctx.lineWidth = 1;
|
||||
ctx.globalAlpha = 0.6;
|
||||
|
||||
for (let x = -this.gridRange; x <= this.gridRange; x++) {
|
||||
for (let z = 0; z <= this.gridRange * 2; z++) {
|
||||
const p1 = this.getPoint(x, z);
|
||||
const p2 = this.getPoint(x + 1, z);
|
||||
const p3 = this.getPoint(x, z + 1);
|
||||
|
||||
const proj1 = p1.project();
|
||||
const proj2 = p2.project();
|
||||
const proj3 = p3.project();
|
||||
|
||||
if (p1.isWater || p2.isWater || p3.isWater) {
|
||||
ctx.strokeStyle = '#00ccff';
|
||||
ctx.globalAlpha = 0.5;
|
||||
} else {
|
||||
ctx.strokeStyle = '#00ff00';
|
||||
ctx.globalAlpha = 0.6;
|
||||
}
|
||||
|
||||
if (x < this.gridRange) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(proj1.x, proj1.y);
|
||||
ctx.lineTo(proj2.x, proj2.y);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
if (z < this.gridRange * 2) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(proj1.x, proj1.y);
|
||||
ctx.lineTo(proj3.x, proj3.y);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
}
|
||||
|
||||
class Airplane {
|
||||
constructor() {
|
||||
this.z = 80;
|
||||
this.vx = 1.5;
|
||||
this.roll = 0;
|
||||
this.pitch = 0;
|
||||
this.yaw = 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 = (Date.now() / 1000) * 0.3;
|
||||
this.roll = Math.sin(Date.now() / 800) * 0.4;
|
||||
this.pitch = Math.cos(Date.now() / 600) * 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>
|
||||
Reference in new issue
Block a user