diff --git a/assets/index.html b/assets/index.html
index 0ccb187..85cd56a 100644
--- a/assets/index.html
+++ b/assets/index.html
@@ -37,22 +37,23 @@
height = canvas.height = window.innerHeight;
}
- function rotatePoint(point, angle) {
- const cos = Math.cos(angle);
- const sin = Math.sin(angle);
- return {
- x: point.x * cos - point.z * sin,
- z: point.x * sin + point.z * cos
- };
- }
-
window.addEventListener('resize', resize);
resize();
const camera = {
- fov: 600
+ fov: 800
};
+ function rotateY(point, angle) {
+ const cos = Math.cos(angle);
+ const sin = Math.sin(angle);
+ return {
+ x: point.x * cos - point.z * sin,
+ y: point.y,
+ z: point.x * sin + point.z * cos
+ };
+ }
+
class Point3D {
constructor(x, y, z) {
this.x = x;
@@ -61,22 +62,21 @@
}
project() {
- const rotated = rotatePoint(this, Math.PI);
const scale = camera.fov / (camera.fov + this.z);
- const xIso = rotated.x - rotated.z;
- const yIso = (rotated.x + rotated.z) * 0.5;
+ 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 / 2;
return { x: x2d, y: y2d, scale: scale };
}
}
- class Triangle {
- constructor(p1, p2, p3) {
+ class TriangleMesh {
+ constructor(p1, p2, p3, color) {
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
- this.calculateNormal();
+ this.color = color;
}
calculateNormal() {
@@ -93,88 +93,71 @@
this.normal.y /= len;
this.normal.z /= len;
}
- const light = { x: 0.3, y: 0.3, z: 0.8 };
+ const light = { x: 0.3, y: 0.5, z: 0.8 };
const dot = this.normal.x * light.x + this.normal.y * light.y + this.normal.z * light.z;
- this.intensity = Math.max(0.2, dot);
+ this.intensity = Math.max(0.15, 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;
+ isBackface() {
+ return this.normal.y > 0.5;
}
- drawFilled() {
+ draw() {
+ if (this.isBackface()) return;
+
const proj1 = this.p1.project();
const proj2 = this.p2.project();
const proj3 = this.p3.project();
- if (this.isBackfaceCulled()) return;
-
- const colorVal = Math.floor(20 + this.intensity * 180);
- const color = this.p1.isWater || this.p2.isWater || this.p3.isWater
- ? `rgba(0, ${Math.floor(colorVal * 0.8)}, ${colorVal + 50}, 0.7)`
- : `rgba(0, ${colorVal}, 0, 0.7)`;
+ const colorVal = Math.floor(this.intensity * 200);
+ const r = this.color.r || 0;
+ const g = this.color.g || (this.color.isWater ? 100 : colorVal);
+ const b = this.color.b || (this.color.isWater ? 200 : 0);
+ const fillColor = `rgba(${Math.floor(r * this.intensity)}, ${Math.floor(g * this.intensity)}, ${Math.floor(b * this.intensity)}, 0.85)`;
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.fillStyle = fillColor;
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.strokeStyle = this.color.isWater ? '#00ccff' : (this.color.r ? '#ff3333' : '#00ff66');
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 = 100;
+ this.gridSize = 120;
this.gridRange = 30;
this.offsetX = 0;
this.offsetZ = 0;
this.triangles = [];
- this.waterLevel = 50;
+ this.waterLevel = 20;
}
generateHeight(x, z) {
- const nx = x / 200;
- const nz = z / 200;
+ const nx = x / 300;
+ const nz = z / 300;
let y = 0;
- y += Math.sin(nx) * 40;
- y += Math.cos(nz) * 40;
- y += Math.sin(nx * 2 + nz * 0.5) * 20;
- y += Math.cos(nz * 2 - nx) * 20;
- y += Math.sin(nx * 3 - nz * 2) * 10;
+ y += Math.sin(nx * 0.7) * 60;
+ y += Math.cos(nz * 0.7) * 60;
+ y += Math.sin(nx * 1.5 + nz * 0.3) * 30;
+ y += Math.cos(nz * 1.5 - nx * 0.4) * 25;
+ y += Math.sin(nx * 2.5 - nz * 1.8) * 10;
return y;
}
- isWater(x, z, y) {
+ isWater(y) {
return y < this.waterLevel;
}
update() {
- this.offsetX -= 2;
- this.offsetZ -= 1;
+ this.offsetX -= 1.5;
+ this.offsetZ -= 0.7;
this.generateTriangles();
}
@@ -190,103 +173,168 @@
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);
+ const isWater1 = this.isWater(p1.y);
+ const color1 = isWater1 ? { isWater: true, r: 0, g: 0, b: 180 } : { r: 0, g: 180, b: 0 };
+ const isWater2 = this.isWater(p2.y);
+ const color2 = isWater2 ? { isWater: true, r: 0, g: 0, b: 180 } : { r: 0, g: 180, b: 0 };
+ const isWater3 = this.isWater(p3.y);
+ const color3 = isWater3 ? { isWater: true, r: 0, g: 0, b: 180 } : { r: 0, g: 180, b: 0 };
+ const isWater4 = this.isWater(p4.y);
+ const color4 = isWater4 ? { isWater: true, r: 0, g: 0, b: 180 } : { r: 0, g: 180, b: 0 };
- this.triangles.push(tri1);
- this.triangles.push(tri2);
+ this.triangles.push(new TriangleMesh(p1, p2, p3));
+ this.triangles.push(new TriangleMesh(p1, p3, p4));
}
}
+
+ this.triangles.forEach(t => t.calculateNormal());
}
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;
+ return new Point3D(worldX, y, worldZ);
}
- draw() {
- this.triangles.forEach(tri => tri.drawFilled());
- this.triangles.forEach(tri => tri.drawWireframe());
+ render() {
+ this.triangles.sort((a, b) => {
+ const za = (a.p1.z + a.p2.z + a.p3.z) / 3;
+ const zb = (b.p1.z + b.p2.z + b.p3.z) / 3;
+ return zb - za;
+ });
+ this.triangles.forEach(tri => tri.draw());
}
}
class Airplane {
constructor() {
- this.z = 120;
- this.yaw = 0;
- this.roll = 0;
- this.pitch = 0;
- this.wireframe = [];
+ this.vertices = [];
+ this.faces = [];
this.init();
}
init() {
- const w = 20, h = 8, d = 30;
- 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 verts = [];
+ const faces = [];
- 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]
- ];
+ const fuselageLength = 80;
+ const fuselageRadius = 8;
+ const segments = 12;
- this.wireframe = { vertices, edges };
+ for (let i = 0; i < segments; i++) {
+ const angle = (i / segments) * Math.PI * 2;
+ verts.push(new Point3D(
+ Math.cos(angle) * fuselageRadius,
+ Math.sin(angle) * fuselageRadius,
+ 0
+ ));
+ }
+
+ for (let i = 0; i < segments; i++) {
+ const angle1 = (i / segments) * Math.PI * 2;
+ const angle2 = ((i + 1) / segments) * Math.PI * 2;
+ verts.push(new Point3D(
+ Math.cos(angle1) * fuselageRadius,
+ Math.sin(angle1) * fuselageRadius,
+ fuselageLength
+ ));
+ verts.push(new Point3D(
+ Math.cos(angle2) * fuselageRadius,
+ Math.sin(angle2) * fuselageRadius,
+ fuselageLength
+ ));
+ }
+
+ verts.push(new Point3D(0, 0, fuselageLength + 30));
+
+ const baseIdx = 0;
+ const middleStart = segments;
+ const middleBase = middleStart;
+ const tipIdx = verts.length - 1;
+
+ for (let i = 0; i < segments; i++) {
+ const next = (i + 1) % segments;
+ const middleStart = segments + i * 2;
+ const middleNext = segments + ((i + 1) % segments) * 2;
+
+ faces.push([baseIdx + i, baseIdx + next, tipIdx]);
+ faces.push([middleStart, middleNext, middleStart + 1]);
+ faces.push([middleStart + 1, middleNext, middleNext + 1]);
+ }
+
+ const wingSpan = 100;
+ const wingChord = 25;
+ const wingY = -2;
+
+ const wingBackLeft = verts.length;
+ verts.push(new Point3D(-wingSpan / 2, wingY, 10));
+ verts.push(new Point3D(-wingSpan / 2 + 5, wingY, 35));
+ verts.push(new Point3D(wingSpan / 2 - 5, wingY, 35));
+ verts.push(new Point3D(wingSpan / 2, wingY, 10));
+
+ faces.push([wingBackLeft, wingBackLeft + 1, wingBackLeft + 2]);
+ faces.push([wingBackLeft, wingBackLeft + 2, wingBackLeft + 3]);
+
+ const tailSpan = 40;
+ const tailChord = 15;
+ const tailY = 12;
+
+ const tailBottom = verts.length;
+ verts.push(new Point3D(-tailSpan / 2, tailY, fuselageLength - 30));
+ verts.push(new Point3D(-tailSpan / 2 + 3, tailY, fuselageLength - 5));
+ verts.push(new Point3D(tailSpan / 2 - 3, tailY, fuselageLength - 5));
+ verts.push(new Point3D(tailSpan / 2, tailY, fuselageLength - 30));
+
+ faces.push([tailBottom, tailBottom + 1, tailBottom + 2]);
+ faces.push([tailBottom, tailBottom + 2, tailBottom + 3]);
+
+ const vertFinTop = verts.length;
+ verts.push(new Point3D(0, tailY + 25, fuselageLength - 25));
+ verts.push(new Point3D(0, tailY + 25, fuselageLength - 5));
+
+ faces.push([vertFinTop, vertFinTop + 1, middleStart]);
+
+ this.vertices = verts;
+ this.faces = faces;
}
update() {
this.yaw = Math.sin(Date.now() / 1000) * 0.15;
- this.roll = Math.sin(Date.now() / 600) * 0.3;
- this.pitch = Math.cos(Date.now() / 800) * 0.2;
+ this.roll = Math.sin(Date.now() / 600) * 0.2;
+ this.pitch = Math.cos(Date.now() / 800) * 0.15;
}
- draw() {
+ render() {
+ const transformedVerts = this.vertices.map(v => {
+ const rot = rotateY(v, this.yaw);
+ const cosR = Math.cos(this.roll), sinR = Math.sin(this.roll);
+ const x1 = rot.x * cosR - rot.y * sinR;
+ const y1 = rot.x * sinR + rot.y * cosR;
+ const cosP = Math.cos(this.pitch), sinP = Math.sin(this.pitch);
+ const y2 = y1 * cosP - rot.z * sinP;
+ const z2 = y1 * sinP + rot.z * cosP;
+ return new Point3D(x1, y2, 150 + z2);
+ });
+
ctx.strokeStyle = '#ff0000';
- ctx.lineWidth = 2.5;
+ 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;
+ this.faces.forEach((face) => {
+ const p1 = transformedVerts[face[0]];
+ const p2 = transformedVerts[face[1]];
+ const p3 = transformedVerts[face[2]];
- 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();
+ ctx.moveTo(p1.project().x, p1.project().y);
+ ctx.lineTo(p2.project().x, p2.project().y);
+ ctx.lineTo(p3.project().x, p3.project().y);
+ ctx.closePath();
+ ctx.stroke();
});
- 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;
}
}
@@ -299,10 +347,10 @@
ctx.fillRect(0, 0, width, height);
landscape.update();
- landscape.draw();
+ landscape.render();
airplane.update();
- airplane.draw();
+ airplane.render();
requestAnimationFrame(animate);
}
@@ -310,4 +358,4 @@
animate();