awacke1's picture
Create app.py
6ea2eb4 verified
raw
history blame
9.01 kB
import streamlit as st
import streamlit.components.v1 as components
st.set_page_config(page_title="City Evolution Simulator", layout="wide")
st.title("City Evolution Simulator")
st.write("Watch a city grow using L-Grammar and Three.js")
html_code = """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>City Evolution Simulator</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; display: block; }
.ui-panel {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.7);
padding: 15px;
border-radius: 5px;
color: white;
font-family: Arial, sans-serif;
z-index: 1000;
}
.ui-panel button {
margin: 5px 0;
padding: 5px 10px;
width: 100%;
background: #4CAF50;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
.ui-panel button:hover { background: #45a049; }
</style>
</head>
<body>
<div class="ui-panel">
<h3>City Controls</h3>
<button id="evolve">Evolve City</button>
<button id="reset">Reset View</button>
<div id="stats">
<p>Buildings: <span id="building-count">0</span></p>
<p>Generation: <span id="generation">0</span></p>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
<script>
class CityLGrammar {
constructor() {
this.axiom = "F";
this.rules = {
"F": ["F[+F]F[-F]", "F[+F][-F]F", "FF", "F[+F]"],
"+": ["+"],
"-": ["-"],
"[": ["["],
"]": ["]"]
};
this.generation = 0;
this.angle = Math.PI / 4;
this.currentString = this.axiom;
}
evolve() {
this.generation++;
let result = this.currentString;
let newString = "";
for (let char of result) {
if (this.rules[char]) {
const possibleRules = this.rules[char];
const selectedRule = possibleRules[Math.floor(Math.random() * possibleRules.length)];
newString += selectedRule;
} else {
newString += char;
}
}
this.currentString = newString;
return this.currentString;
}
buildCity(scene) {
while(scene.children.length > 2) { // Keep lights
scene.remove(scene.children[2]);
}
const stack = [];
let position = new THREE.Vector3(0, 0, 0);
let direction = new THREE.Vector3(0, 1, 0);
let buildingCount = 0;
// Add ground plane
const groundGeo = new THREE.PlaneGeometry(100, 100);
const groundMat = new THREE.MeshPhongMaterial({ color: 0x4a7043 });
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.1;
scene.add(ground);
for (let char of this.currentString) {
switch(char) {
case 'F':
const height = 2 + Math.random() * 6;
const width = 1 + Math.random() * 2;
// Create building
const buildingGeo = new THREE.BoxGeometry(width, height, width);
const buildingMat = new THREE.MeshPhongMaterial({
color: new THREE.Color(0.5 + Math.random() * 0.5,
0.5 + Math.random() * 0.5,
0.5 + Math.random() * 0.5),
shininess: 30
});
const building = new THREE.Mesh(buildingGeo, buildingMat);
building.position.copy(position);
building.position.y = height / 2;
building.rotation.y = Math.random() * Math.PI;
scene.add(building);
position.add(direction.clone().multiplyScalar(height));
buildingCount++;
break;
case '+':
direction.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.angle);
break;
case '-':
direction.applyAxisAngle(new THREE.Vector3(0, 0, 1), -this.angle);
break;
case '[':
stack.push({
position: position.clone(),
direction: direction.clone()
});
break;
case ']':
if (stack.length > 0) {
const state = stack.pop();
position = state.position;
direction = state.direction;
}
break;
}
}
document.getElementById('building-count').textContent = buildingCount;
document.getElementById('generation').textContent = this.generation;
}
}
// Three.js setup
let scene, camera, renderer, controls;
let city;
function init() {
const container = document.body;
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
// Camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 20, 30);
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
// Lights
scene.add(new THREE.AmbientLight(0x404040));
const sun = new THREE.DirectionalLight(0xffffff, 0.8);
sun.position.set(50, 50, 50);
scene.add(sun);
// Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.target.set(0, 0, 0);
// Initialize city
city = new CityLGrammar();
city.buildCity(scene);
// Events
window.addEventListener('resize', onWindowResize);
document.getElementById('evolve').addEventListener('click', evolveCity);
document.getElementById('reset').addEventListener('click', resetView);
animate();
}
function evolveCity() {
city.evolve();
city.buildCity(scene);
}
function resetView() {
camera.position.set(0, 20, 30);
controls.target.set(0, 0, 0);
controls.update();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
init();
</script>
</body>
</html>
"""
# Render the HTML component
components.html(html_code, height=600)
st.sidebar.title("City Evolution Simulator")
st.sidebar.write("""
## How to Play
Watch a city evolve through generations using L-System grammar rules.
### Controls:
- **Evolve City**: Generate next city generation
- **Reset View**: Return to default view
- **Left-click + drag**: Rotate view
- **Right-click + drag**: Pan view
- **Scroll**: Zoom in/out
### Evolution Rules:
- Each generation adds more buildings
- Buildings branch out from existing structures
- Random heights and sizes create variety
- Roads implicitly form between buildings
Track progress with building count and generation number!
""")