Custom Code Experiment •
Particle Landscape
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>3D Particle Landscape</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background: #000;
}
canvas {
display: block;
}
#info {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-family: monospace;
font-size: 12px;
z-index: 100;
background: rgba(0, 0, 0, 0.5);
padding: 10px;
border-radius: 5px;
}
#controls {
position: absolute;
bottom: 10px;
left: 10px;
color: white;
font-family: monospace;
font-size: 12px;
z-index: 100;
background: rgba(0, 0, 0, 0.5);
padding: 10px;
border-radius: 5px;
}
button {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 5px 10px;
margin: 5px;
border-radius: 3px;
cursor: pointer;
}
button:hover {
background: rgba(255, 255, 255, 0.2);
}
</style>
</head>
<body>
<div id="info">Loading 3D Particle Landscape...</div>
<div id="controls">
<button id="reset">Reset</button>
</div>
<canvas id="canvas"></canvas>
<script>
// Simple 3D Particle Landscape
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const info = document.getElementById('info');
let width = window.innerWidth;
let height = window.innerHeight;
canvas.width = width;
canvas.height = height;
// Mouse position
let mouseX = width / 2;
let mouseY = height / 2;
let mouseDown = false;
// Camera settings
let camera = {
x: 0,
y: 0,
z: 500,
rotationX: 0,
rotationY: 0,
fov: 600
};
// 3D Particle class
class Particle3D {
constructor(x, y, z, type = 'ground') {
this.original = { x, y, z };
this.x = x;
this.y = y;
this.z = z;
this.type = type;
this.size = 2;
this.speed = Math.random() * 0.02 + 0.01;
this.waveOffset = Math.random() * Math.PI * 2;
this.color = this.getColor();
}
getColor() {
switch(this.type) {
case 'mountain':
return this.z > 100 ? '#ffffff' : // Snow
this.z > 50 ? '#888888' : // Rock
'#4a6fa5'; // Mountain
case 'tree':
return '#2d5a27'; // Dark green
case 'water':
return '#4287f5'; // Blue water
case 'star':
return '#ffffff'; // White stars
case 'foliage':
return '#3e8c3e'; // Green foliage
default: // ground
const hue = 120 + Math.random() * 40; // Green range
return `hsl(${hue}, 70%, ${30 + Math.random() * 20}%)`;
}
}
update(time) {
// Return to original position with wave motion
const wave = Math.sin(time + this.waveOffset) * 5;
this.x += (this.original.x - this.x) * this.speed;
this.y += (this.original.y - this.y) * this.speed + wave;
this.z += (this.original.z - this.z) * this.speed;
// Special behaviors
if (this.type === 'star') {
this.twinkle = Math.sin(time * 2 + this.waveOffset) * 0.5 + 0.5;
this.size = 1 + this.twinkle * 3;
}
if (this.type === 'tree') {
this.y += Math.sin(time * 3 + this.waveOffset) * 2;
}
if (this.type === 'water') {
this.y += Math.sin(time * 5 + this.waveOffset) * 1;
}
}
project() {
// Simple 3D projection
const scale = camera.fov / (camera.fov + this.z + camera.z);
const x2d = (this.x + camera.x) * scale + width / 2;
const y2d = (this.y + camera.y) * scale + height / 2;
return {
x: x2d,
y: y2d,
scale: scale,
visible: scale > 0
};
}
}
// Create landscape
let particles = [];
let time = 0;
function createLandscape() {
particles = [];
// Create ground
for (let x = -400; x <= 400; x += 15) {
for (let z = -200; z <= 200; z += 15) {
const height = Math.sin(x * 0.01) * 20 + Math.cos(z * 0.015) * 15;
const y = -height - 50;
particles.push(new Particle3D(x, y, z, 'ground'));
}
}
// Create mountains
for (let i = 0; i < 3; i++) {
const centerX = (Math.random() - 0.5) * 600;
const centerZ = (Math.random() - 0.5) * 300;
const radius = 80 + Math.random() * 60;
const peakHeight = 80 + Math.random() * 70;
for (let a = 0; a < Math.PI * 2; a += 0.2) {
for (let r = 0; r < radius; r += 10) {
const x = centerX + Math.cos(a) * r;
const z = centerZ + Math.sin(a) * r;
const height = peakHeight * (1 - r / radius) * (Math.sin(a * 3) * 0.3 + 0.7);
particles.push(new Particle3D(x, -height, z, 'mountain'));
}
}
}
// Create water (lake/river)
for (let x = -100; x <= 100; x += 10) {
for (let z = -50; z <= 50; z += 10) {
const y = -30;
if (Math.sqrt(x*x + z*z) < 80) {
particles.push(new Particle3D(x, y, z, 'water'));
}
}
}
// Create trees
for (let i = 0; i < 20; i++) {
const x = (Math.random() - 0.5) * 500;
const z = (Math.random() - 0.5) * 250;
const y = -60;
// Tree trunk
particles.push(new Particle3D(x, y, z, 'tree'));
// Tree foliage
for (let j = 0; j < 3; j++) {
particles.push(new Particle3D(
x + (Math.random() - 0.5) * 10,
y - 5 - j * 8,
z + (Math.random() - 0.5) * 10,
'foliage'
));
}
}
// Create stars
for (let i = 0; i < 100; i++) {
particles.push(new Particle3D(
(Math.random() - 0.5) * 2000,
(Math.random() - 0.5) * 1000 - 300,
(Math.random() - 0.5) * 1000,
'star'
));
}
updateInfo();
}
// 3D projection helper
function projectPoint(x, y, z) {
const scale = camera.fov / (camera.fov + z + camera.z);
return {
x: (x + camera.x) * scale + width / 2,
y: (y + camera.y) * scale + height / 2,
scale: scale
};
}
// Draw functions
function drawParticle(particle, proj) {
if (!proj.visible) return;
ctx.beginPath();
ctx.fillStyle = particle.color;
const size = particle.size * proj.scale;
if (particle.type === 'star') {
// Draw star as twinkling circle
ctx.arc(proj.x, proj.y, size, 0, Math.PI * 2);
ctx.fill();
} else {
// Draw landscape particles as squares (faster)
ctx.fillRect(proj.x - size/2, proj.y - size/2, size, size);
}
}
function drawConnection(p1, p2, proj1, proj2) {
if (!proj1.visible || !proj2.visible) return;
const distance = Math.sqrt(
Math.pow(p2.x - p1.x, 2) +
Math.pow(p2.y - p1.y, 2) +
Math.pow(p2.z - p1.z, 2)
);
if (distance < 30) {
ctx.strokeStyle = `rgba(100, 150, 255, ${0.1 * (1 - distance/30)})`;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(proj1.x, proj1.y);
ctx.lineTo(proj2.x, proj2.y);
ctx.stroke();
}
}
// Animation loop
function animate() {
// Clear with gradient sky
const gradient = ctx.createLinearGradient(0, 0, 0, height);
gradient.addColorStop(0, '#0a192f'); // Dark blue top
gradient.addColorStop(0.5, '#1a3a5f'); // Medium blue
gradient.addColorStop(0.7, '#2a5a8f'); // Light blue
gradient.addColorStop(1, '#0a192f'); // Dark blue bottom
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
time += 0.01;
// Update camera with mouse interaction
if (mouseDown) {
camera.rotationY += (mouseX - width/2) * 0.0001;
camera.rotationX += (mouseY - height/2) * 0.0001;
}
// Camera movement
camera.x = Math.sin(time * 0.1) * 50;
camera.z = 400 + Math.sin(time * 0.05) * 50;
// Update and sort particles by depth
particles.forEach(p => p.update(time));
// Sort by Z for proper depth rendering
particles.sort((a, b) => (b.z + camera.z) - (a.z + camera.z));
// Draw particles
particles.forEach(particle => {
const proj = particle.project();
drawParticle(particle, proj);
});
// Draw connections between nearby particles (creates mesh effect)
ctx.globalAlpha = 0.1;
for (let i = 0; i < particles.length; i += 5) {
for (let j = i + 1; j < Math.min(i + 10, particles.length); j++) {
if (particles[i].type === particles[j].type) {
const proj1 = particles[i].project();
const proj2 = particles[j].project();
drawConnection(particles[i], particles[j], proj1, proj2);
}
}
}
ctx.globalAlpha = 1;
// Update info
updateInfo();
requestAnimationFrame(animate);
}
function updateInfo() {
info.innerHTML = `
Particles: ${particles.length}<br>
Camera: ${camera.x.toFixed(1)}, ${camera.y.toFixed(1)}, ${camera.z.toFixed(1)}
`;
}
// Event listeners
document.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
});
document.addEventListener('mousedown', () => {
mouseDown = true;
});
document.addEventListener('mouseup', () => {
mouseDown = false;
});
document.addEventListener('wheel', (e) => {
camera.z += e.deltaY * 0.5;
camera.z = Math.max(100, Math.min(1000, camera.z));
});
// Control buttons (only reset remains)
document.getElementById('reset').addEventListener('click', () => {
camera = {
x: 0,
y: 0,
z: 500,
rotationX: 0,
rotationY: 0,
fov: 600
};
createLandscape();
});
// Handle resize
window.addEventListener('resize', () => {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
});
// Initialize and start
createLandscape();
animate();
</script>
</body>
</html>