infinite-agents-public/threejs_viz/threejs_viz_7.html

297 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js - Interactive Crystal Garden</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
}
#info {
position: absolute;
top: 10px;
left: 10px;
color: white;
background: rgba(0, 0, 0, 0.7);
padding: 15px;
border-radius: 8px;
font-size: 14px;
max-width: 400px;
z-index: 100;
}
#info h2 {
margin: 0 0 10px 0;
font-size: 18px;
}
#info .web-source {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid rgba(255,255,255,0.3);
font-size: 12px;
opacity: 0.8;
}
#controls-hint {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: white;
background: rgba(0, 0, 0, 0.6);
padding: 10px 20px;
border-radius: 20px;
font-size: 12px;
text-align: center;
}
</style>
</head>
<body>
<div id="info">
<h2>Interactive Crystal Garden</h2>
<p><strong>Technique:</strong> OrbitControls for immersive 3D exploration</p>
<p><strong>Learning:</strong> Implemented smooth camera controls with damping, zoom limits, and rotation constraints to create an intuitive exploration experience of a procedurally generated crystal formation.</p>
<div class="web-source">
<strong>Web Source:</strong><br>
<a href="https://threejs.org/docs/examples/en/controls/OrbitControls.html" target="_blank" style="color: #4fc3f7;">Three.js OrbitControls Documentation</a><br>
<em>Applied: enableDamping (0.05), zoom limits (5-50), polar angle constraints (Math.PI/6 to Math.PI/2), and autoRotate for subtle animation</em>
</div>
</div>
<div id="controls-hint">
🖱️ Left Click + Drag: Rotate | Right Click + Drag: Pan | Scroll: Zoom
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Scene setup
let camera, scene, renderer, controls;
let crystals = [];
let time = 0;
init();
animate();
function init() {
// Camera setup - positioned to view the crystal garden from an interesting angle
camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(15, 12, 15);
// Scene with dark background to make crystals pop
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a1a);
scene.fog = new THREE.Fog(0x0a0a1a, 30, 60);
// Renderer with antialiasing for smooth edges
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// OrbitControls setup - This is the core learning from the web source
controls = new OrbitControls(camera, renderer.domElement);
// Enable damping for smooth, inertial movement
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// Set zoom limits for optimal viewing experience
controls.minDistance = 5;
controls.maxDistance = 50;
// Constrain vertical rotation to prevent upside-down views
controls.minPolarAngle = Math.PI / 6; // 30 degrees from top
controls.maxPolarAngle = Math.PI / 2; // 90 degrees (horizon)
// Enable subtle auto-rotation for dynamic presentation
controls.autoRotate = true;
controls.autoRotateSpeed = 0.5;
// Set target to center of scene
controls.target.set(0, 3, 0);
// Update controls after manual camera positioning
controls.update();
// Create the crystal garden visualization
createCrystalGarden();
// Lighting setup
createLighting();
// Handle resize
window.addEventListener('resize', onWindowResize);
}
function createCrystalGarden() {
// Create ground plane
const groundGeometry = new THREE.CircleGeometry(25, 64);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x1a1a2e,
roughness: 0.8,
metalness: 0.2
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Create multiple crystal clusters at different positions
const clusterPositions = [
{ x: 0, z: 0, count: 12, radius: 4 },
{ x: -8, z: 6, count: 8, radius: 3 },
{ x: 10, z: -4, count: 6, radius: 2.5 },
{ x: -6, z: -8, count: 7, radius: 2.8 },
{ x: 8, z: 8, count: 9, radius: 3.2 }
];
clusterPositions.forEach(cluster => {
createCrystalCluster(cluster.x, cluster.z, cluster.count, cluster.radius);
});
}
function createCrystalCluster(centerX, centerZ, count, radius) {
const colors = [
0x4fc3f7, // Cyan
0x9c27b0, // Purple
0xff6b6b, // Pink
0x4ecdc4, // Teal
0xf06292 // Rose
];
for (let i = 0; i < count; i++) {
// Random position within cluster radius
const angle = (i / count) * Math.PI * 2 + Math.random() * 0.5;
const dist = Math.random() * radius;
const x = centerX + Math.cos(angle) * dist;
const z = centerZ + Math.sin(angle) * dist;
// Random crystal height and size
const height = 2 + Math.random() * 6;
const baseRadius = 0.3 + Math.random() * 0.5;
// Create octahedron for crystal shape (two pyramids)
const geometry = new THREE.OctahedronGeometry(baseRadius, 0);
// Scale vertically to create elongated crystal
geometry.scale(1, height / baseRadius, 1);
// Random color from palette
const color = colors[Math.floor(Math.random() * colors.length)];
const material = new THREE.MeshPhysicalMaterial({
color: color,
metalness: 0.3,
roughness: 0.1,
transparent: true,
opacity: 0.85,
envMapIntensity: 1.0,
clearcoat: 1.0,
clearcoatRoughness: 0.1
});
const crystal = new THREE.Mesh(geometry, material);
crystal.position.set(x, height / 2, z);
// Random rotation for variety
crystal.rotation.y = Math.random() * Math.PI * 2;
crystal.rotation.z = (Math.random() - 0.5) * 0.2;
crystal.castShadow = true;
crystal.receiveShadow = true;
scene.add(crystal);
crystals.push({
mesh: crystal,
baseY: height / 2,
floatSpeed: 0.5 + Math.random() * 0.5,
floatOffset: Math.random() * Math.PI * 2
});
}
}
function createLighting() {
// Ambient light for base illumination
const ambientLight = new THREE.AmbientLight(0x404080, 0.3);
scene.add(ambientLight);
// Main directional light (simulating sun/moon)
const mainLight = new THREE.DirectionalLight(0xffffff, 1.0);
mainLight.position.set(10, 20, 10);
mainLight.castShadow = true;
mainLight.shadow.camera.left = -20;
mainLight.shadow.camera.right = 20;
mainLight.shadow.camera.top = 20;
mainLight.shadow.camera.bottom = -20;
mainLight.shadow.mapSize.width = 2048;
mainLight.shadow.mapSize.height = 2048;
scene.add(mainLight);
// Accent lights for crystal illumination
const accentLight1 = new THREE.PointLight(0x4fc3f7, 1.5, 20);
accentLight1.position.set(-8, 5, 6);
scene.add(accentLight1);
const accentLight2 = new THREE.PointLight(0xff6b6b, 1.5, 20);
accentLight2.position.set(10, 5, -4);
scene.add(accentLight2);
const accentLight3 = new THREE.PointLight(0x9c27b0, 1.5, 20);
accentLight3.position.set(0, 8, 0);
scene.add(accentLight3);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
time += 0.01;
// Subtle floating animation for crystals
crystals.forEach(crystal => {
crystal.mesh.position.y = crystal.baseY +
Math.sin(time * crystal.floatSpeed + crystal.floatOffset) * 0.1;
// Gentle rotation
crystal.mesh.rotation.y += 0.002;
});
// CRITICAL: Update controls in animation loop
// Required when enableDamping or autoRotate are true
controls.update();
renderer.render(scene, camera);
}
</script>
</body>
</html>