awacke1's picture
Update app.py
8ac7a4d verified
raw
history blame
11.9 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 3D city grow with lakes, hills, and evolving blocks")
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>Blocks: <span id="block-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 BuildingLSystem {
constructor() {
this.axiom = "F";
this.rules = {
"F": ["F[+F]", "F[-F]", "FF", "F"],
"+": ["+"],
"-": ["-"],
"[": ["["],
"]": ["]"]
};
this.angle = Math.PI / 6;
}
generate() {
let result = this.axiom;
for (let i = 0; i < 2; i++) {
let newString = "";
for (let char of result) {
if (this.rules[char]) {
const possible = this.rules[char];
newString += possible[Math.floor(Math.random() * possible.length)];
} else {
newString += char;
}
}
result = newString;
}
return result;
}
build(scene, basePos, maxHeight) {
let height = 0;
const stack = [];
let position = basePos.clone();
let direction = new THREE.Vector3(0, 1, 0);
const structure = new THREE.Group();
for (let char of this.generate()) {
switch(char) {
case 'F':
if (height < maxHeight) {
const width = 0.8 + Math.random() * 0.4;
const floorHeight = 1 + Math.random() * 2;
const geo = new THREE.BoxGeometry(width, floorHeight, width);
const mat = 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)
});
const floor = new THREE.Mesh(geo, mat);
floor.position.copy(position).add(new THREE.Vector3(0, floorHeight/2, 0));
structure.add(floor);
position.y += floorHeight;
height += floorHeight;
}
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.clone());
break;
case ']':
if (stack.length > 0) position = stack.pop();
break;
}
}
return structure;
}
}
class CitySimulator {
constructor() {
this.blocks = [];
this.blockSize = 10; // 10x10 units per block
this.maxBuildingsPerBlock = 5;
this.generation = 0;
this.lakeCenters = [
new THREE.Vector2(20, 20),
new THREE.Vector2(-30, 10)
];
}
addBlock(scene, x, z) {
const block = {
position: new THREE.Vector2(x, z),
buildings: [],
maxHeight: this.isWaterfront(x, z) ? 15 : 8
};
this.blocks.push(block);
this.evolveBlock(scene, block, true);
}
isWaterfront(x, z) {
const pos = new THREE.Vector2(x, z);
return this.lakeCenters.some(center =>
pos.distanceTo(center) < 15 && pos.distanceTo(center) > 5);
}
evolveBlock(scene, block, initial = false) {
if (block.buildings.length < this.maxBuildingsPerBlock) {
const lsystem = new BuildingLSystem();
const gridX = Math.floor(Math.random() * 3) - 1;
const gridZ = Math.floor(Math.random() * 3) - 1;
const basePos = new THREE.Vector3(
block.position.x + gridX * 2,
this.getTerrainHeight(block.position.x, block.position.y),
block.position.y + gridZ * 2
);
const building = lsystem.build(scene, basePos, block.maxHeight);
if (this.isWaterfront(block.position.x, block.position.y)) {
building.scale.set(1.5, 2, 1.5); // Lavish waterfront buildings
}
scene.add(building);
block.buildings.push(building);
}
}
evolve() {
this.generation++;
if (this.blocks.length < 20) {
const x = (Math.random() - 0.5) * 140;
const z = (Math.random() - 0.5) * 80;
if (!this.isInLake(x, z)) this.addBlock(scene, x, z);
}
this.blocks.forEach(block => this.evolveBlock(scene, block));
this.updateStats();
}
getTerrainHeight(x, z) {
return Math.sin(x * 0.05) * Math.cos(z * 0.05) * 5;
}
isInLake(x, z) {
const pos = new THREE.Vector2(x, z);
return this.lakeCenters.some(center => pos.distanceTo(center) < 10);
}
updateStats() {
const totalBuildings = this.blocks.reduce((sum, block) => sum + block.buildings.length, 0);
document.getElementById('building-count').textContent = totalBuildings;
document.getElementById('block-count').textContent = this.blocks.length;
document.getElementById('generation').textContent = this.generation;
}
}
let scene, camera, renderer, controls;
let city;
function init() {
const container = document.body;
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
// Camera with 16:9 aspect ratio
camera = new THREE.PerspectiveCamera(75, 16 / 9, 0.1, 1000);
camera.position.set(0, 50, 80);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight * (9/16));
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);
// Terrain and Lakes
const groundGeo = new THREE.PlaneGeometry(160, 90, 32, 32);
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);
// Add lakes
const lakeGeo = new THREE.CircleGeometry(10, 32);
const lakeMat = new THREE.MeshPhongMaterial({ color: 0x4682b4 });
city.lakeCenters.forEach(center => {
const lake = new THREE.Mesh(lakeGeo, lakeMat);
lake.rotation.x = -Math.PI / 2;
lake.position.set(center.x, 0, center.y);
scene.add(lake);
});
// Add bridges
const bridgeGeo = new THREE.BoxGeometry(5, 0.2, 15);
const bridgeMat = new THREE.MeshPhongMaterial({ color: 0x808080 });
const bridge1 = new THREE.Mesh(bridgeGeo, bridgeMat);
bridge1.position.set(15, 0.2, 20);
scene.add(bridge1);
// Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.target.set(0, 0, 0);
// Initialize city
city = new CitySimulator();
city.addBlock(scene, 0, 0);
// Events
window.addEventListener('resize', onWindowResize);
document.getElementById('evolve').addEventListener('click', () => city.evolve());
document.getElementById('reset').addEventListener('click', resetView);
animate();
}
function resetView() {
camera.position.set(0, 50, 80);
controls.target.set(0, 0, 0);
controls.update();
}
function onWindowResize() {
const width = window.innerWidth;
const height = width * (9/16);
camera.aspect = 16 / 9;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
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 3D city evolve with lakes, hills, and building blocks.
### Controls:
- **Evolve City**: Grow the city
- **Reset View**: Return to default view
- **Left-click + drag**: Rotate
- **Right-click + drag**: Pan
- **Scroll**: Zoom
### Features:
- 16:9 play area
- Blocks (10x10 units) with up to 5 buildings
- Buildings evolve floor-by-floor using L-systems
- Terrain with hills and lakes
- Waterfront properties grow larger
- Bridges connect land masses
""")