anycoder-499d02ee / index.html
andorxotnot's picture
Upload folder using huggingface_hub
48589a9 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stable Organic Life 3D</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;500;700&display=swap" rel="stylesheet">
<style>
:root {
--primary: #ff9f43; /* Warmer, amber tone for organic feel */
--glass-bg: rgba(15, 15, 20, 0.75);
--glass-border: rgba(255, 255, 255, 0.08);
--text: #f1f5f9;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
}
body {
overflow: hidden;
background-color: #050505;
font-family: 'Inter', sans-serif;
color: var(--text);
}
/* Canvas */
#canvas-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
background: radial-gradient(circle at center, #1a1a20 0%, #000000 100%);
}
/* Header Link */
.brand-link {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 100;
color: var(--text);
text-decoration: none;
font-weight: 600;
font-size: 0.85rem;
background: var(--glass-bg);
padding: 8px 20px;
border-radius: 30px;
border: 1px solid var(--glass-border);
backdrop-filter: blur(12px);
transition: all 0.3s ease;
letter-spacing: 0.5px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}
.brand-link:hover {
background: rgba(255, 255, 255, 0.1);
transform: translateX(-50%) translateY(-2px);
border-color: rgba(255, 255, 255, 0.2);
}
/* UI Overlay */
.ui-panel {
position: fixed;
top: 20px;
right: 20px;
width: 340px;
background: var(--glass-bg);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid var(--glass-border);
border-radius: 20px;
padding: 24px;
z-index: 10;
display: flex;
flex-direction: column;
gap: 18px;
box-shadow: 0 20px 40px rgba(0,0,0,0.4);
max-height: 90vh;
overflow-y: auto;
}
.panel-header h1 {
font-size: 1.2rem;
font-weight: 700;
background: linear-gradient(135deg, #fff, #ff9f43);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 4px;
}
.panel-header p {
font-size: 0.75rem;
color: #94a3b8;
}
.control-group {
display: flex;
flex-direction: column;
gap: 10px;
}
label {
font-size: 0.8rem;
color: #cbd5e1;
display: flex;
justify-content: space-between;
font-weight: 500;
}
.value-tag {
color: var(--primary);
font-family: monospace;
}
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 6px;
background: rgba(255,255,255,0.05);
border-radius: 3px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--primary);
cursor: pointer;
transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
border: 2px solid rgba(0,0,0,0.2);
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.3);
}
.btn-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
button {
background: rgba(255,255,255,0.05);
border: 1px solid var(--glass-border);
color: #e2e8f0;
padding: 12px;
border-radius: 12px;
cursor: pointer;
font-weight: 600;
font-size: 0.85rem;
transition: all 0.2s ease;
font-family: 'Inter', sans-serif;
}
button:hover {
background: rgba(255,255,255,0.1);
transform: translateY(-1px);
border-color: rgba(255,255,255,0.2);
}
button:active {
transform: translateY(0);
}
button.primary {
background: var(--primary);
color: #0f172a;
border: none;
}
button.primary:hover {
background: #ffaf5f;
}
.stats {
font-size: 0.7rem;
color: #64748b;
text-align: center;
padding-top: 10px;
border-top: 1px solid var(--glass-border);
}
.checkbox-group {
display: flex;
align-items: center;
gap: 10px;
font-size: 0.8rem;
color: #cbd5e1;
cursor: pointer;
}
.checkbox-group input {
accent-color: var(--primary);
width: 16px;
height: 16px;
}
/* Loading overlay */
#loading {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: #000;
z-index: 200;
display: flex;
justify-content: center;
align-items: center;
color: white;
transition: opacity 0.5s;
pointer-events: none;
}
@media (max-width: 600px) {
.ui-panel {
width: calc(100% - 40px);
bottom: 20px;
top: auto;
max-height: 60vh;
}
}
</style>
<!-- Three.js and dependencies -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
</head>
<body>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="brand-link">Built with anycoder</a>
<div id="loading">Initializing Simulation...</div>
<div id="canvas-container"></div>
<div class="ui-panel" id="uiPanel">
<div class="panel-header">
<h1>Stable Organic Life</h1>
<p>Complex Cellular Structure Engine</p>
</div>
<div class="control-group">
<label>Particle Count <span id="val-count" class="value-tag">1600</span></label>
<input type="range" id="inp-count" min="500" max="3000" step="100" value="1600">
</div>
<div class="control-group">
<label>Interaction Radius <span id="val-radius" class="value-tag">60</span></label>
<input type="range" id="inp-radius" min="20" max="120" value="60">
</div>
<div class="control-group">
<label>Force Strength <span id="val-force" class="value-tag">1.0</span></label>
<input type="range" id="inp-force" min="0.1" max="3.0" step="0.1" value="1.0">
</div>
<div class="control-group">
<label>Friction (Stability) <span id="val-friction" class="value-tag">0.82</span></label>
<input type="range" id="inp-friction" min="0.50" max="0.95" step="0.01" value="0.82">
</div>
<div class="control-group">
<label>Speed Limit <span id="val-speed" class="value-tag">2.0</span></label>
<input type="range" id="inp-speed" min="0.5" max="5.0" step="0.1" value="2.0">
</div>
<label class="checkbox-group">
<input type="checkbox" id="inp-symmetric" checked>
Symmetric Rules (Required for Stability)
</label>
<button id="btn-randomize" class="primary">🎲 Randomize Rules</button>
<div class="btn-grid">
<button id="btn-stable">🦠 Stable Life</button>
<button id="btn-clusters">🌌 Clusters</button>
</div>
<div class="stats" id="stats">
FPS: 60 | Types: 6
</div>
</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
// --- Configuration ---
const CONFIG = {
particleCount: 1600,
types: 6,
radius: 60, // Increased for larger structures
forceFactor: 1.0, // Lower force prevents explosions
friction: 0.82, // Lower friction = more fluid, Higher = more rigid
worldSize: 250,
symmetric: true,
maxSpeed: 2.0 // NEW: Hard cap on velocity to prevent instability
};
// --- State ---
let particles = [];
let rules = [];
let typeColors = [];
// --- Three.js Setup ---
const container = document.getElementById('canvas-container');
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x050505, 0.002); // Deep dark fog
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 150, 300);
const renderer = new THREE.WebGLRenderer({ antialias: false, powerPreference: "high-performance" });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
container.appendChild(renderer.domElement);
// Post-processing (Bloom)
const renderScene = new RenderPass(scene, camera);
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
bloomPass.threshold = 0.15;
bloomPass.strength = 0.6; // Subtle bloom for matte objects
bloomPass.radius = 0.8;
const composer = new EffectComposer(renderer);
composer.addPass(renderScene);
composer.addPass(bloomPass);
// Controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.autoRotate = true;
controls.autoRotateSpeed = 0.2;
// Lighting
const ambientLight = new THREE.AmbientLight(0x333333);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
dirLight.position.set(50, 100, 50);
scene.add(dirLight);
const backLight = new THREE.DirectionalLight(0x4444ff, 0.5);
backLight.position.set(-50, -50, -50);
scene.add(backLight);
const fillLight = new THREE.DirectionalLight(0xffaa00, 0.3);
fillLight.position.set(0, 0, 100);
scene.add(fillLight);
// --- Particle System ---
const geometry = new THREE.SphereGeometry(1, 16, 16); // Slightly more detail
// Matte Material (Frosted Look)
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
roughness: 0.9,
metalness: 0.0,
emissive: 0x000000,
});
let instancedMesh;
const dummy = new THREE.Object3D();
const _color = new THREE.Color();
// --- Logic ---
function initSystem() {
if (instancedMesh) {
scene.remove(instancedMesh);
instancedMesh.dispose();
}
// Adjusted Palette for better visibility of structures
typeColors = [
new THREE.Color(0xff6b6b), // Red
new THREE.Color(0x4ecdc4), // Cyan
new THREE.Color(0xffbe76), // Orange
new THREE.Color(0xff9ff3), // Pink
new THREE.Color(0x54a0ff), // Blue
new THREE.Color(0x78e08f) // Green
];
if (CONFIG.types > typeColors.length) CONFIG.types = typeColors.length;
particles = new Float32Array(CONFIG.particleCount * 8);
for (let i = 0; i < CONFIG.particleCount; i++) {
const i8 = i * 8;
// Position
particles[i8] = (Math.random() - 0.5) * CONFIG.worldSize;
particles[i8+1] = (Math.random() - 0.5) * CONFIG.worldSize;
particles[i8+2] = (Math.random() - 0.5) * CONFIG.worldSize;
// Velocity
particles[i8+3] = 0;
particles[i8+4] = 0;
particles[i8+5] = 0;
// Type
particles[i8+6] = Math.floor(Math.random() * CONFIG.types);
// Size (Base size)
particles[i8+7] = 1.5 + Math.random() * 1.0;
}
instancedMesh = new THREE.InstancedMesh(geometry, material, CONFIG.particleCount);
instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
scene.add(instancedMesh);
randomizeRules();
// Remove loading screen
document.getElementById('loading').style.opacity = 0;
}
function randomizeRules() {
rules = [];
for (let i = 0; i < CONFIG.types; i++) {
rules[i] = [];
for (let j = 0; j < CONFIG.types; j++) {
// Bias towards slightly negative (repulsive) to prevent total collapse
// but allow specific strong attractions
rules[i][j] = (Math.random() * 2 - 1);
}
}
// Enforce Symmetry for Stability
if (CONFIG.symmetric) {
for (let i = 0; i < CONFIG.types; i++) {
for (let j = i + 1; j < CONFIG.types; j++) {
rules[j][i] = rules[i][j];
}
// Self-interaction tends to be stable if slightly repulsive or weakly attractive
rules[i][i] = Math.min(rules[i][i], 0.5);
}
}
console.log("Rules Generated. Symmetric:", CONFIG.symmetric);
}
function setPreset(name) {
if (name === 'stable') {
// Settings for large, complex, cell-like structures
CONFIG.symmetric = true;
CONFIG.friction = 0.82; // Good damping
CONFIG.forceFactor = 1.0; // Moderate force
CONFIG.radius = 60; // Large radius = Large structures
CONFIG.maxSpeed = 2.0;
document.getElementById('inp-symmetric').checked = true;
randomizeRules();
} else if (name === 'clusters') {
// Settings for tighter, denser clusters
CONFIG.symmetric = true;
CONFIG.friction = 0.70; // Slippery
CONFIG.forceFactor = 1.5;
CONFIG.radius = 40;
CONFIG.maxSpeed = 3.0;
document.getElementById('inp-symmetric').checked = true;
randomizeRules();
}
updateUiValues();
}
// --- Physics Loop ---
function updatePhysics() {
const count = CONFIG.particleCount;
const rMax = CONFIG.radius;
const rMaxSq = rMax * rMax;
const forceFactor = CONFIG.forceFactor;
const friction = CONFIG.friction;
const worldSize = CONFIG.worldSize;
const halfWorld = worldSize / 2;
const maxSpeed = CONFIG.maxSpeed;
for (let i = 0; i < count; i++) {
const i8 = i * 8;
let fx = 0, fy = 0, fz = 0;
const typeI = particles[i8+6];
const px = particles[i8];
const py = particles[i8+1];
const pz = particles[i8+2];
for (let j = 0; j < count; j++) {
if (i === j) continue;
const j8 = j * 8;
let dx = particles[j8] - px;
let dy = particles[j8+1] - py;
let dz = particles[j8+2] - pz;
// Wrap around (Toroidal world)
if (dx > halfWorld) dx -= worldSize;
if (dx < -halfWorld) dx += worldSize;
if (dy > halfWorld) dy -= worldSize;
if (dy < -halfWorld) dy += worldSize;
if (dz > halfWorld) dz -= worldSize;
if (dz < -halfWorld) dz += worldSize;
const distSq = dx*dx + dy*dy + dz*dz;
if (distSq > 0 && distSq < rMaxSq) {
const dist = Math.sqrt(distSq);
const q = dist / rMax;
const typeJ = particles[j8+6];
let f = 0;
// Modified Force Curve for Stability
if (q < 0.25) {
// Strong Repulsion zone (prevents collapse)
f = (q / 0.25 - 1.0) * 2.0;
} else {
// Interaction zone
// Standard Particle Life: f = val * (1 - Math.abs(2 * q - 1));
// Smoother curve:
const val = rules[typeI][typeJ];
f = val * (1.0 - q) * 2.0; // Linear fade out
// Or the classic peak at mid-distance
// f = val * Math.max(0, 1.0 - Math.abs(2.0 * q - 1.0 - 0.2));
}
const fScaled = (f * forceFactor) / dist;
fx += dx * fScaled;
fy += dy * fScaled;
fz += dz * fScaled;
}
}
// Apply Forces
particles[i8+3] += fx;
particles[i8+4] += fy;
particles[i8+5] += fz;
// Friction
particles[i8+3] *= friction;
particles[i8+4] *= friction;
particles[i8+5] *= friction;
// SPEED LIMIT (CRITICAL FOR STABILITY)
const vx = particles[i8+3];
const vy = particles[i8+4];
const vz = particles[i8+5];
const speed = Math.sqrt(vx*vx + vy*vy + vz*vz);
if (speed > maxSpeed) {
const ratio = maxSpeed / speed;
particles[i8+3] *= ratio;
particles[i8+4] *= ratio;
particles[i8+5] *= ratio;
}
// Move
particles[i8] += particles[i8+3];
particles[i8+1] += particles[i8+4];
particles[i8+2] += particles[i8+5];
// Boundary Wrap
if (particles[i8] <= -halfWorld) particles[i8] += worldSize;
if (particles[i8] >= halfWorld) particles[i8] -= worldSize;
if (particles[i8+1] <= -halfWorld) particles[i8+1] += worldSize;
if (particles[i8+1] >= halfWorld) particles[i8+1] -= worldSize;
if (particles[i8+2] <= -halfWorld) particles[i8+2] += worldSize;
if (particles[i8+2] >= halfWorld) particles[i8+2] -= worldSize;
}
}
function updateVisuals() {
const count = CONFIG.particleCount;
for (let i = 0; i < count; i++) {
const i8 = i * 8;
dummy.position.set(
particles[i8],
particles[i8+1],
particles[i8+2]
);
// Visual scale based on type and slight pulse
const baseSize = particles[i8+7];
dummy.scale.set(baseSize, baseSize, baseSize);
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
const type = particles[i8+6];
_color.copy(typeColors[type]);
// Slight color variation based on velocity
const speed = Math.sqrt(particles[i8+3]**2 + particles[i8+4]**2 + particles[i8+5]**2);
const normalizedSpeed = Math.min(speed / CONFIG.maxSpeed, 1.0);
// Brighten slightly when moving fast
_color.r += normalizedSpeed * 0.2;
_color.g += normalizedSpeed * 0.2;
_color.b += normalizedSpeed * 0.2;
instancedMesh.setColorAt(i, _color);
}
instancedMesh.instanceMatrix.needsUpdate = true;
if(instancedMesh.instanceColor) instancedMesh.instanceColor.needsUpdate = true;
}
// --- Main Loop ---
const statsEl = document.getElementById('stats');
let frames = 0;
let fpsTime = 0;
function animate(time) {
requestAnimationFrame(animate);
frames++;
if (time - fpsTime > 1000) {
statsEl.innerHTML = `FPS: ${frames} | Particles: ${CONFIG.particleCount} | Limit: ${CONFIG.maxSpeed}`;
frames = 0;
fpsTime = time;
}
updatePhysics();
updateVisuals();
controls.update();
composer.render();
}
// --- UI Event Listeners ---
document.getElementById('inp-count').addEventListener('change', (e) => {
CONFIG.particleCount = parseInt(e.target.value);
document.getElementById('val-count').innerText = CONFIG.particleCount;
initSystem();
});
document.getElementById('inp-radius').addEventListener('input', (e) => {
CONFIG.radius = parseInt(e.target.value);
document.getElementById('val-radius').innerText = CONFIG.radius;
});
document.getElementById('inp-force').addEventListener('input', (e) => {
CONFIG.forceFactor = parseFloat(e.target.value);
document.getElementById('val-force').innerText = CONFIG.forceFactor;
});
document.getElementById('inp-friction').addEventListener('input', (e) => {
CONFIG.friction = parseFloat(e.target.value);
document.getElementById('val-friction').innerText = CONFIG.friction;
});
document.getElementById('inp-speed').addEventListener('input', (e) => {
CONFIG.maxSpeed = parseFloat(e.target.value);
document.getElementById('val-speed').innerText = CONFIG.maxSpeed;
});
document.getElementById('inp-symmetric').addEventListener('change', (e) => {
CONFIG.symmetric = e.target.checked;
});
document.getElementById('btn-randomize').addEventListener('click', () => {
randomizeRules();
});
document.getElementById('btn-stable').addEventListener('click', () => {
setPreset('stable');
});
document.getElementById('btn-clusters').addEventListener('click', () => {
setPreset('clusters');
});
function updateUiValues() {
document.getElementById('inp-friction').value = CONFIG.friction;
document.getElementById('val-friction').innerText = CONFIG.friction;
document.getElementById('inp-force').value = CONFIG.forceFactor;
document.getElementById('val-force').innerText = CONFIG.forceFactor;
document.getElementById('inp-radius').value = CONFIG.radius;
document.getElementById('val-radius').innerText = CONFIG.radius;
document.getElementById('inp-speed').value = CONFIG.maxSpeed;
document.getElementById('val-speed').innerText = CONFIG.maxSpeed;
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
});
initSystem();
animate();
</script>
</body>
</html>