Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Three.js Spaceship Fly-Through</title> | |
<style> | |
body { margin: 0; overflow: hidden; } | |
canvas { display: block; } | |
</style> | |
</head> | |
<body> | |
<script type="importmap"> | |
{ | |
"imports": { | |
"three": "https://unpkg.com/[email protected]/build/three.module.js", | |
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/" | |
} | |
} | |
</script> | |
<script type="module"> | |
import * as THREE from 'three'; | |
import { WebGLRenderer } from 'three'; | |
// Scene setup | |
const scene = new THREE.Scene(); | |
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
camera.position.set(0, 5, 10); | |
const renderer = new WebGLRenderer({ antialias: true }); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
renderer.setPixelRatio(window.devicePixelRatio); | |
document.body.appendChild(renderer.domElement); | |
// Lighting | |
const ambientLight = new THREE.AmbientLight(0x404040, 0.5); | |
scene.add(ambientLight); | |
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); | |
directionalLight.position.set(1, 1, 1); | |
scene.add(directionalLight); | |
// Spaceship (simple cone) | |
const shipGeometry = new THREE.ConeGeometry(0.5, 2, 32); | |
const shipMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00, metalness: 0.8, roughness: 0.2 }); | |
const spaceship = new THREE.Mesh(shipGeometry, shipMaterial); | |
spaceship.position.set(0, 0, 0); | |
scene.add(spaceship); | |
// Particle system for buildings | |
const buildingCount = 50; // Reduced for performance | |
const buildingGeometry = new THREE.BufferGeometry(); | |
const buildingPositions = new Float32Array(buildingCount * 3); | |
const buildingColors = new Float32Array(buildingCount * 3); | |
const buildingSizes = new Float32Array(buildingCount); | |
for (let i = 0; i < buildingCount; i++) { | |
buildingPositions[i * 3] = (Math.random() - 0.5) * 200; | |
buildingPositions[i * 3 + 1] = Math.random() * 10; | |
buildingPositions[i * 3 + 2] = (Math.random() - 0.5) * 200; | |
buildingColors[i * 3] = 0.2; | |
buildingColors[i * 3 + 1] = 0.6; | |
buildingColors[i * 3 + 2] = 1.0; | |
buildingSizes[i] = Math.random() * 2 + 1; | |
} | |
buildingGeometry.setAttribute('position', new THREE.BufferAttribute(buildingPositions, 3)); | |
buildingGeometry.setAttribute('color', new THREE.BufferAttribute(buildingColors, 3)); | |
buildingGeometry.setAttribute('size', new THREE.BufferAttribute(buildingSizes, 1)); | |
const buildingMaterial = new THREE.PointsMaterial({ | |
size: 2, | |
vertexColors: true, | |
transparent: true, | |
opacity: 0.7, | |
sizeAttenuation: true | |
}); | |
const buildings = new THREE.Points(buildingGeometry, buildingMaterial); | |
scene.add(buildings); | |
// Particle system for people | |
const peopleCount = 100; // Reduced for performance | |
const peopleGeometry = new THREE.BufferGeometry(); | |
const peoplePositions = new Float32Array(peopleCount * 3); | |
const peopleColors = new Float32Array(peopleCount * 3); | |
const peopleSizes = new Float32Array(peopleCount); | |
for (let i = 0; i < peopleCount; i++) { | |
peoplePositions[i * 3] = (Math.random() - 0.5) * 150; | |
peoplePositions[i * 3 + 1] = Math.random() * 5; | |
peoplePositions[i * 3 + 2] = (Math.random() - 0.5) * 150; | |
peopleColors[i * 3] = 1.0; | |
peopleColors[i * 3 + 1] = 0.8; | |
peopleColors[i * 3 + 2] = 0.8; | |
peopleSizes[i] = Math.random() * 0.5 + 0.2; | |
} | |
peopleGeometry.setAttribute('position', new THREE.BufferAttribute(peoplePositions, 3)); | |
peopleGeometry.setAttribute('color', new THREE.BufferAttribute(peopleColors, 3)); | |
peopleGeometry.setAttribute('size', new THREE.BufferAttribute(peopleSizes, 1)); | |
const peopleMaterial = new THREE.PointsMaterial({ | |
size: 0.5, | |
vertexColors: true, | |
transparent: true, | |
opacity: 0.6, | |
sizeAttenuation: true | |
}); | |
const people = new THREE.Points(peopleGeometry, peopleMaterial); | |
scene.add(people); | |
// Instanced cubes | |
const cubeCount = 30; // Reduced for performance | |
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1); | |
const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0xff00ff, metalness: 0.5, roughness: 0.4 }); | |
const cubeMesh = new THREE.InstancedMesh(cubeGeometry, cubeMaterial, cubeCount); | |
const dummy = new THREE.Object3D(); | |
for (let i = 0; i < cubeCount; i++) { | |
dummy.position.set( | |
(Math.random() - 0.5) * 100, | |
Math.random() * 10, | |
(Math.random() - 0.5) * 100 | |
); | |
dummy.scale.setScalar(Math.random() * 0.5 + 0.5); | |
dummy.updateMatrix(); | |
cubeMesh.setMatrixAt(i, dummy.matrix); | |
} | |
scene.add(cubeMesh); | |
// Camera movement | |
let time = 0; | |
function updateCamera() { | |
time += 0.01; | |
camera.position.z -= 0.5; // Forward movement | |
camera.position.x = Math.sin(time * 0.5) * 5; // Side-to-side | |
camera.position.y = Math.cos(time * 0.3) * 2 + 5; // Up-down | |
camera.lookAt(new THREE.Vector3(0, 0, camera.position.z - 100)); | |
} | |
// Parallax and repetition | |
function updateObjects() { | |
const positions = buildings.geometry.attributes.position.array; | |
for (let i = 0; i < buildingCount; i++) { | |
positions[i * 3 + 2] += 0.5; | |
if (positions[i * 3 + 2] > camera.position.z + 100) { | |
positions[i * 3 + 2] -= 200; | |
} | |
} | |
buildings.geometry.attributes.position.needsUpdate = true; | |
const peoplePos = people.geometry.attributes.position.array; | |
for (let i = 0; i < peopleCount; i++) { | |
peoplePos[i * 3 + 2] += 0.7; // Faster for parallax | |
if (peoplePos[i * 3 + 2] > camera.position.z + 100) { | |
peoplePos[i * 3 + 2] -= 200; | |
} | |
} | |
people.geometry.attributes.position.needsUpdate = true; | |
for (let i = 0; i < cubeCount; i++) { | |
cubeMesh.getMatrixAt(i, dummy.matrix); | |
dummy.matrix.decompose(dummy.position, dummy.quaternion, dummy.scale); | |
dummy.position.z += 0.6; | |
if (dummy.position.z > camera.position.z + 100) { | |
dummy.position.z -= 200; | |
} | |
dummy.updateMatrix(); | |
cubeMesh.setMatrixAt(i, dummy.matrix); | |
} | |
cubeMesh.instanceMatrix.needsUpdate = true; | |
} | |
// Animation loop | |
function animate() { | |
try { | |
requestAnimationFrame(animate); | |
updateCamera(); | |
updateObjects(); | |
renderer.render(scene, camera); | |
} catch (error) { | |
console.error('Rendering error:', error); | |
} | |
} | |
animate(); | |
// Handle window resize | |
window.addEventListener('resize', () => { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
}); | |
// Error handling for WebGL | |
if (!renderer.getContext()) { | |
console.error('WebGL is not supported in this browser.'); | |
alert('WebGL is not supported. Please use a modern browser like Chrome or Firefox.'); | |
} | |
</script> | |
</body> | |
</html> |