sunset-racing-opus / js /environment.js
victor's picture
victor HF Staff
Add car racing game
66242a7
// ═══════════════════════════════════════════════════════
// ENVIRONMENT β€” sky dome, lights, ground
// ═══════════════════════════════════════════════════════
import * as THREE from 'three';
export function createSky(scene) {
const skyGeo = new THREE.SphereGeometry(400, 32, 32);
const skyMat = new THREE.ShaderMaterial({
side: THREE.BackSide,
depthWrite: false,
uniforms: {
uTop: { value: new THREE.Color(0x1a1a3e) }, // deep indigo top
uMid: { value: new THREE.Color(0xb07088) }, // muted mauve
uBot: { value: new THREE.Color(0xffa860) }, // warm amber horizon
uSun: { value: new THREE.Color(0xffee88) }, // warm sun glow
uSunDir: { value: new THREE.Vector3(0.6, 0.01, 0.3).normalize() }, // sun right at horizon
},
vertexShader: `
varying vec3 vWorldPos;
void main() {
vWorldPos = (modelMatrix * vec4(position, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform vec3 uTop, uMid, uBot, uSun;
uniform vec3 uSunDir;
varying vec3 vWorldPos;
void main() {
vec3 dir = normalize(vWorldPos);
float y = dir.y;
vec3 col = mix(uMid, uTop, smoothstep(0.1, 0.8, y));
col = mix(uBot, col, smoothstep(-0.02, 0.15, y));
float sunDot = max(dot(dir, uSunDir), 0.0);
col += uSun * pow(sunDot, 64.0) * 2.0;
col += uSun * pow(sunDot, 6.0) * 0.4;
col += vec3(1.0, 0.8, 0.5) * pow(sunDot, 3.0) * 0.15;
gl_FragColor = vec4(col, 1.0);
}
`,
});
scene.add(new THREE.Mesh(skyGeo, skyMat));
}
export function createLights(scene) {
scene.add(new THREE.AmbientLight(0x999088, 0.45));
scene.add(new THREE.HemisphereLight(0xffccaa, 0x443322, 0.3));
const sun = new THREE.DirectionalLight(0xffe8cc, 1.4);
sun.position.set(120, 6, 50);
sun.castShadow = true;
sun.shadow.mapSize.set(2048, 2048);
sun.shadow.bias = -0.0005;
const sc = sun.shadow.camera;
sc.left = sc.bottom = -130;
sc.right = sc.top = 130;
sc.near = 1; sc.far = 300;
scene.add(sun);
scene.add(sun.target);
const fill = new THREE.DirectionalLight(0x8877aa, 0.2);
fill.position.set(-40, 30, -60);
scene.add(fill);
return { sun };
}
export function createGround(scene, renderer) {
const texSize = 512;
const texCanvas = document.createElement('canvas');
texCanvas.width = texCanvas.height = texSize;
const ctx = texCanvas.getContext('2d');
ctx.fillStyle = '#4a7a3a';
ctx.fillRect(0, 0, texSize, texSize);
const imgData = ctx.getImageData(0, 0, texSize, texSize);
const d = imgData.data;
for (let i = 0; i < d.length; i += 4) {
const n = (Math.random() - 0.5) * 30;
d[i] = Math.max(0, Math.min(255, d[i] + n * 0.6));
d[i+1] = Math.max(0, Math.min(255, d[i+1] + n));
d[i+2] = Math.max(0, Math.min(255, d[i+2] + n * 0.4));
}
for (let i = 0; i < 600; i++) {
const x = Math.random() * texSize, y = Math.random() * texSize;
const r = 2 + Math.random() * 4;
ctx.fillStyle = Math.random() > 0.7 ? '#3d6a2a' : '#5a8f40';
ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2); ctx.fill();
}
for (let i = 0; i < 60; i++) {
const x = Math.random() * texSize, y = Math.random() * texSize;
ctx.fillStyle = ['#ff8866', '#ffcc44', '#ffaa77', '#cc7744'][Math.floor(Math.random() * 4)];
ctx.beginPath(); ctx.arc(x, y, 1.5, 0, Math.PI * 2); ctx.fill();
}
ctx.putImageData(imgData, 0, 0);
const grassTex = new THREE.CanvasTexture(texCanvas);
grassTex.wrapS = grassTex.wrapT = THREE.RepeatWrapping;
grassTex.repeat.set(40, 40);
grassTex.anisotropy = renderer.capabilities.getMaxAnisotropy();
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(800, 800),
new THREE.MeshLambertMaterial({ map: grassTex })
);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.1;
ground.receiveShadow = true;
scene.add(ground);
}