Files

313 lines
11 KiB
JavaScript
Raw Permalink 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.
// Dream City — tren, arabalar, yayalar
// Kural: hepsi kendi yolunda döner; kullanıcı uyurken de şehir yaşamaya devam eder.
import * as THREE from '../vendor/three.module.js';
import { ADIM, YARIM, YOL_GENIS } from './city.js';
import { aralik, sans, sec, rast } from './utils.js';
const KENAR = YARIM * ADIM; // şehrin iç sınırı
export class Hareket {
constructor(sahne, sehir) {
this.sahne = sahne;
this.sehir = sehir;
this.grup = new THREE.Group();
sahne.add(this.grup);
this._raylar();
this._tren();
this._arabalar();
this._yayalar();
}
// ---------- raylar (şehrin dış çeperi) ----------
_raylar() {
// Raylar şehrin DIŞINDA dolaşsın (blokların üstünden geçmesin)
const yarı = YARIM * ADIM + ADIM * 0.5 + 4;
const rayMat = this.sehir.matRay;
const traversMat = new THREE.MeshStandardMaterial({ color: 0x5a4638, roughness: 1 });
// dört kenar boyunca çift ray
const kenarlar = [
{ x: 0, z: -yarı, rot: 0, len: (yarı + 2) * 2 },
{ x: 0, z: yarı, rot: 0, len: (yarı + 2) * 2 },
{ x: -yarı, z: 0, rot: Math.PI / 2, len: (yarı + 2) * 2 },
{ x: yarı, z: 0, rot: Math.PI / 2, len: (yarı + 2) * 2 },
];
for (const k of kenarlar) {
for (const ofs of [-0.42, 0.42]) {
const ray = new THREE.Mesh(new THREE.BoxGeometry(k.len, 0.09, 0.13), rayMat);
ray.position.set(k.x + (k.rot ? ofs : 0), 0.12, k.z + (k.rot ? 0 : ofs));
ray.rotation.y = k.rot;
ray.castShadow = false;
this.grup.add(ray);
}
// traversler
const adet = Math.floor(k.len / 0.85);
for (let i = 0; i < adet; i++) {
const t = -k.len / 2 + i * 0.85;
const tr = new THREE.Mesh(new THREE.BoxGeometry(0.32, 0.07, 1.25), traversMat);
if (k.rot) {
tr.position.set(k.x, 0.07, k.z + t);
tr.rotation.y = Math.PI / 2;
} else {
tr.position.set(k.x + t, 0.07, k.z);
}
this.grup.add(tr);
}
}
// çeper boyunca taş set
this.rayYari = yarı;
}
// ---------- tren ----------
_tren() {
const vagonlar = [];
const lokomotifMat = new THREE.MeshStandardMaterial({ color: 0x2f4f4f, roughness: 0.55, metalness: 0.35 });
const govdeMat = new THREE.MeshStandardMaterial({ color: 0x93402f, roughness: 0.6, metalness: 0.15 });
const camMat = new THREE.MeshStandardMaterial({
color: 0x2a2f3a, emissive: 0xffc27a, emissiveIntensity: 0.9, roughness: 0.3,
});
const lokomotif = new THREE.Group();
const g1 = new THREE.Mesh(new THREE.BoxGeometry(1.6, 0.72, 0.92), lokomotifMat);
g1.position.y = 0.62;
g1.castShadow = true;
lokomotif.add(g1);
const baca = new THREE.Mesh(new THREE.CylinderGeometry(0.14, 0.18, 0.38, 8), lokomotifMat);
baca.position.set(0.5, 1.14, 0);
lokomotif.add(baca);
const kabin = new THREE.Mesh(new THREE.BoxGeometry(0.62, 0.52, 0.86), lokomotifMat);
kabin.position.set(-0.5, 1.08, 0);
lokomotif.add(kabin);
// far
const far = new THREE.Mesh(
new THREE.SphereGeometry(0.11, 8, 6),
new THREE.MeshStandardMaterial({ color: 0xfff2cf, emissive: 0xffe0a0, emissiveIntensity: 2 })
);
far.position.set(0.82, 0.66, 0);
lokomotif.add(far);
lokomotif.userData.far = far;
vagonlar.push(lokomotif);
for (let i = 0; i < 3; i++) {
const v = new THREE.Group();
const govde = new THREE.Mesh(new THREE.BoxGeometry(1.25, 0.62, 0.86), govdeMat);
govde.position.y = 0.6;
govde.castShadow = true;
v.add(govde);
for (let p = -1; p <= 1; p++) {
const cam = new THREE.Mesh(new THREE.PlaneGeometry(0.28, 0.3), camMat.clone());
cam.position.set(p * 0.38, 0.72, 0.431);
v.add(cam);
const cam2 = cam.clone();
cam2.position.z = -0.431;
cam2.rotation.y = Math.PI;
v.add(cam2);
}
const cati = new THREE.Mesh(new THREE.BoxGeometry(1.3, 0.1, 0.9), lokomotifMat);
cati.position.y = 0.95;
v.add(cati);
vagonlar.push(v);
}
// tekerlekler her vagonda
const tekerMat = new THREE.MeshStandardMaterial({ color: 0x1d2026, roughness: 0.8 });
for (const v of vagonlar) {
for (const wx of [-0.42, 0.42]) {
for (const wz of [-0.4, 0.4]) {
const t = new THREE.Mesh(new THREE.CylinderGeometry(0.19, 0.19, 0.1, 10), tekerMat);
t.rotation.x = Math.PI / 2;
t.position.set(wx, 0.19, wz);
v.add(t);
}
}
v.position.y = 0.16;
v.userData.ofs = -vagonlar.indexOf(v) * 2.1;
this.grup.add(v);
}
this.tren = {
vagonlar,
hiz: 0.075, // birim/kare
t: 0,
rayYari: this.rayYari,
};
}
_trenPozisyonu(t) {
// kare çevre üzerinde ilerle: 0..1
const y = this.tren.rayYari;
const cevre = 8 * y;
let d = ((t % 1) + 1) % 1 * cevre;
const kenar = 2 * y;
if (d < kenar) return { x: -y + d, z: -y, rot: Math.PI / 2 }; // üst kenar → sağa
d -= kenar;
if (d < kenar) return { x: y, z: -y + d, rot: 0 }; // sağ kenar → aşağı
d -= kenar;
if (d < kenar) return { x: y - d, z: y, rot: -Math.PI / 2 }; // alt kenar → sola
d -= kenar;
return { x: -y, z: y - d, rot: Math.PI }; // sol kenar → yukarı
}
// ---------- arabalar ----------
_arabalar() {
const renkler = [0xe05a4a, 0x4a7de0, 0xf0c04a, 0x6ec0a0, 0xf2f2f0, 0xc77bd6, 0x3c4a63];
const arabaSayisi = 12;
for (let i = 0; i < arabaSayisi; i++) {
const a = this._araba(sec(renkler));
this.grup.add(a);
this.sehir.arabalar.push(a);
}
}
_araba(renk) {
const a = new THREE.Group();
const govde = new THREE.Mesh(
new THREE.BoxGeometry(0.86, 0.22, 0.44),
new THREE.MeshStandardMaterial({ color: renk, roughness: 0.45, metalness: 0.25 })
);
govde.position.y = 0.2;
govde.castShadow = true;
a.add(govde);
const kabin = new THREE.Mesh(
new THREE.BoxGeometry(0.44, 0.2, 0.4),
new THREE.MeshStandardMaterial({ color: 0x2b3444, emissive: 0x6fa2c9, emissiveIntensity: 0.12, roughness: 0.35 })
);
kabin.position.set(-0.06, 0.4, 0);
a.add(kabin);
const tekerMat = new THREE.MeshStandardMaterial({ color: 0x16181d, roughness: 0.85 });
const tekerler = [];
for (const tx of [-0.28, 0.28]) {
for (const tz of [-0.23, 0.23]) {
const t = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 0.07, 8), tekerMat);
t.rotation.x = Math.PI / 2;
t.position.set(tx, 0.1, tz);
a.add(t);
tekerler.push(t);
}
}
// farlar
const farMat = new THREE.MeshStandardMaterial({ color: 0xfff4d6, emissive: 0xffe2a8, emissiveIntensity: 1.6 });
for (const tz of [-0.14, 0.14]) {
const far = new THREE.Mesh(new THREE.SphereGeometry(0.055, 6, 5), farMat.clone());
far.position.set(0.44, 0.22, tz);
a.add(far);
}
// araç döngü tanımı
a.userData =
sec([
{ eksen: 'x', hat: -YARIM * ADIM - ADIM / 2 + ADIM, yon: 1, serit: -0.7 },
{ eksen: 'x', hat: -YARIM * ADIM - ADIM / 2 + ADIM, yon: -1, serit: 0.7 },
{ eksen: 'z', hat: 0, yon: 1, serit: 0.7 },
{ eksen: 'z', hat: -ADIM / 2, yon: -1, serit: -0.7 },
]);
a.userData.t = rast();
a.userData.hiz = aralik(0.035, 0.07);
a.userData.tekerler = tekerler;
return a;
}
// ---------- yayalar ----------
_yayalar() {
this.yayaGrubu = new THREE.Group();
this.grup.add(this.yayaGrubu);
const siluetMat = new THREE.MeshStandardMaterial({ color: 0x2a2b33, roughness: 0.9 });
const sapkaMat = new THREE.MeshStandardMaterial({ color: 0x3a3a48, roughness: 0.9 });
for (let i = 0; i < 16; i++) {
const y = new THREE.Group();
const govde = new THREE.Mesh(new THREE.CapsuleGeometry(0.055, 0.18, 4, 6), siluetMat);
govde.position.y = 0.24;
y.add(govde);
const bas = new THREE.Mesh(new THREE.SphereGeometry(0.062, 8, 6), sapkaMat);
bas.position.y = 0.44;
y.add(bas);
const bacakL = new THREE.Mesh(new THREE.CylinderGeometry(0.022, 0.018, 0.16, 5), siluetMat);
bacakL.position.set(0, 0.09, 0.035);
y.add(bacakL);
const bacakR = bacakL.clone();
bacakR.position.z = -0.035;
y.add(bacakR);
y.userData = {
bacakL, bacakR,
eksen: sans(0.5) ? 'x' : 'z',
hat: (Math.floor(aralik(-YARIM, YARIM))) * ADIM + ADIM / 2,
ofs: aralik(-1.35, 1.35),
t: rast(),
hiz: aralik(0.008, 0.02),
yon: sans(0.5) ? 1 : -1,
faz: rast() * 6.28,
};
this.yayaGrubu.add(y);
}
}
// ---------- her karede ----------
guncelle(dt, zaman, gece) {
// ---- TREN ----
const tren = this.tren;
tren.t += dt * tren.hiz * 0.32;
for (const v of tren.vagonlar) {
const p = this._trenPozisyonu(tren.t + v.userData.ofs / (8 * tren.rayYari));
v.position.x = p.x;
v.position.z = p.z;
v.rotation.y = -p.rot + Math.PI / 2;
}
if (tren.vagonlar[0].userData.far) {
tren.vagonlar[0].userData.far.material.emissiveIntensity = 1.4 + gece * 1.6;
}
// ---- ARABALAR ----
const sinir = YARIM * ADIM;
const yolBaslangic = -sinir;
const yolBitis = sinir;
for (const a of this.sehir.arabalar) {
const u = a.userData;
u.t += dt * u.hiz;
const donus = (u.t % 1);
const mesafe = yolBaslangic + donus * (yolBitis - yolBaslangic);
if (u.eksen === 'x') {
a.position.set(u.yon > 0 ? mesafe : yolBitis - (mesafe - yolBaslangic), 0, u.hat + u.serit);
a.rotation.y = u.yon > 0 ? 0 : Math.PI;
} else {
a.position.set(u.hat + u.serit, 0, u.yon > 0 ? mesafe : yolBitis - (mesafe - yolBaslangic));
a.rotation.y = u.yon > 0 ? -Math.PI / 2 : Math.PI / 2;
}
for (const t of u.tekerler) t.rotation.y += dt * 9;
}
// ---- YAYALAR ----
const sin = sinir + 1.2;
for (const y of this.yayaGrubu.children) {
const u = y.userData;
u.t += dt * u.hiz;
const d = (u.t % 1) * (sin * 2) - sin;
const konum = u.yon > 0 ? d : -d;
if (u.eksen === 'x') {
y.position.set(konum, 0.16, u.hat + u.ofs);
y.rotation.y = u.yon > 0 ? 0 : Math.PI;
} else {
y.position.set(u.hat + u.ofs, 0.16, konum);
y.rotation.y = u.yon > 0 ? -Math.PI / 2 : Math.PI / 2;
}
const sal = Math.sin(zaman * 6 + u.faz) * 0.09;
u.bacakL.rotation.x = sal;
u.bacakR.rotation.x = -sal;
// gece silüetleri hafifçe koyulaşsın (ay ışığı altında)
y.position.y = 0.16;
}
}
}