awacke1's picture
Update app.py
1a55305 verified
raw
history blame
13.1 kB
import streamlit as st
import streamlit.components.v1 as components
st.set_page_config(page_title="3D City Evolution Simulator", layout="wide")
st.title("3D City Evolution Simulator")
st.write("Watch a 3D city grow with lakes, hills, and evolving blocks")
# Sliders for container size with initial 3:4 aspect ratio
max_width = min(1200, st.session_state.get('window_width', 1200)) # Use a reasonable max or screen width
max_height = min(1600, st.session_state.get('window_height', 1600)) # Use a reasonable max or screen height
col1, col2 = st.columns(2)
with col1:
container_width = st.slider("Container Width (px)", 300, max_width, 768, step=50)
with col2:
container_height = st.slider("Container Height (px)", 400, max_height, 1024, step=50)
html_code = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>3D City Evolution Simulator</title>
<style>
body {{ margin: 0; overflow: hidden; }}
#container {{ width: {container_width}px; height: {container_height}px; margin: 0 auto; }}
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 id="container"></div>
<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();
let baseWidth = 1.5; // Starting wider base
for (let char of this.generate()) {{
switch(char) {{
case 'F':
if (height < maxHeight) {{
const width = baseWidth * (1 - height / maxHeight); // Narrower as it goes up
const floorHeight = 2 + Math.random() * 2; // Taller floors
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;
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) ? 20 : 12
}};
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);
}}
scene.add(building);
block.buildings.push(building);
}}
}}
evolve(scene) {{
this.generation++;
if (this.blocks.length < 20) {{
const x = (Math.random() - 0.5) * 90; // Adjusted for 3:4
const z = (Math.random() - 0.5) * 120;
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.getElementById('container');
if (!container) {{
console.error('Container not found');
return;
}}
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
// Camera with 3:4 aspect ratio
camera = new THREE.PerspectiveCamera(75, 3 / 4, 0.1, 1000);
camera.position.set(0, 50, 60);
// Renderer
renderer = new THREE.WebGLRenderer({{ antialias: true }});
renderer.setSize({container_width}, {container_height});
container.appendChild(renderer.domElement);
// Lights
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const sun = new THREE.DirectionalLight(0xffffff, 0.8);
sun.position.set(50, 50, 50);
scene.add(sun);
// Ground with 3:4 ratio (90x120)
const groundGeo = new THREE.PlaneGeometry(90, 120, 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);
// Lakes
const lakeGeo = new THREE.CircleGeometry(10, 32);
const lakeMat = new THREE.MeshPhongMaterial({{ color: 0x4682b4 }});
const lakeCenters = [new THREE.Vector2(20, 20), new THREE.Vector2(-30, 10)];
lakeCenters.forEach(center => {{
const lake = new THREE.Mesh(lakeGeo, lakeMat);
lake.rotation.x = -Math.PI / 2;
lake.position.set(center.x, 0.01, center.y);
scene.add(lake);
}});
// Bridge
const bridgeGeo = new THREE.BoxGeometry(5, 0.2, 15);
const bridgeMat = new THREE.MeshPhongMaterial({{ color: 0x808080 }});
const bridge = new THREE.Mesh(bridgeGeo, bridgeMat);
bridge.position.set(15, 0.2, 20);
scene.add(bridge);
// Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.target.set(0, 0, 0);
// City
city = new CitySimulator();
city.addBlock(scene, 0, 0);
// Events
window.addEventListener('resize', onWindowResize);
document.getElementById('evolve').addEventListener('click', () => city.evolve(scene));
document.getElementById('reset').addEventListener('click', resetView);
animate();
}}
function resetView() {{
camera.position.set(0, 50, 60);
controls.target.set(0, 0, 0);
controls.update();
}}
function onWindowResize() {{
const width = {container_width};
const height = {container_height};
camera.aspect = 3 / 4;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}}
function animate() {{
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}}
window.onload = init;
</script>
</body>
</html>
"""
# Render the HTML component with dynamic size
components.html(html_code, width=container_width, height=container_height)
st.sidebar.title("3D 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
- **Sliders**: Adjust play area size
### Features:
- 3:4 initial play area (768x1024)
- Blocks (10x10 units) with up to 5 buildings
- Buildings start wide, grow taller with smaller floors
- Terrain with hills and lakes
- Waterfront properties grow larger
- Bridges connect land masses
""")