awacke1 commited on
Commit
8ac7a4d
Β·
verified Β·
1 Parent(s): 6ea2eb4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +165 -102
app.py CHANGED
@@ -4,7 +4,7 @@ import streamlit.components.v1 as components
4
  st.set_page_config(page_title="City Evolution Simulator", layout="wide")
5
 
6
  st.title("City Evolution Simulator")
7
- st.write("Watch a city grow using L-Grammar and Three.js")
8
 
9
  html_code = """
10
  <!DOCTYPE html>
@@ -46,6 +46,7 @@ html_code = """
46
  <button id="reset">Reset View</button>
47
  <div id="stats">
48
  <p>Buildings: <span id="building-count">0</span></p>
 
49
  <p>Generation: <span id="generation">0</span></p>
50
  </div>
51
  </div>
@@ -53,130 +54,170 @@ html_code = """
53
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
54
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
55
  <script>
56
- class CityLGrammar {
57
  constructor() {
58
  this.axiom = "F";
59
  this.rules = {
60
- "F": ["F[+F]F[-F]", "F[+F][-F]F", "FF", "F[+F]"],
61
  "+": ["+"],
62
  "-": ["-"],
63
  "[": ["["],
64
  "]": ["]"]
65
  };
66
- this.generation = 0;
67
- this.angle = Math.PI / 4;
68
- this.currentString = this.axiom;
69
  }
70
 
71
- evolve() {
72
- this.generation++;
73
- let result = this.currentString;
74
- let newString = "";
75
-
76
- for (let char of result) {
77
- if (this.rules[char]) {
78
- const possibleRules = this.rules[char];
79
- const selectedRule = possibleRules[Math.floor(Math.random() * possibleRules.length)];
80
- newString += selectedRule;
81
- } else {
82
- newString += char;
83
  }
 
84
  }
85
- this.currentString = newString;
86
- return this.currentString;
87
  }
88
 
89
- buildCity(scene) {
90
- while(scene.children.length > 2) { // Keep lights
91
- scene.remove(scene.children[2]);
92
- }
93
-
94
  const stack = [];
95
- let position = new THREE.Vector3(0, 0, 0);
96
  let direction = new THREE.Vector3(0, 1, 0);
97
- let buildingCount = 0;
98
-
99
- // Add ground plane
100
- const groundGeo = new THREE.PlaneGeometry(100, 100);
101
- const groundMat = new THREE.MeshPhongMaterial({ color: 0x4a7043 });
102
- const ground = new THREE.Mesh(groundGeo, groundMat);
103
- ground.rotation.x = -Math.PI / 2;
104
- ground.position.y = -0.1;
105
- scene.add(ground);
106
 
107
- for (let char of this.currentString) {
 
108
  switch(char) {
109
  case 'F':
110
- const height = 2 + Math.random() * 6;
111
- const width = 1 + Math.random() * 2;
112
-
113
- // Create building
114
- const buildingGeo = new THREE.BoxGeometry(width, height, width);
115
- const buildingMat = new THREE.MeshPhongMaterial({
116
- color: new THREE.Color(0.5 + Math.random() * 0.5,
117
- 0.5 + Math.random() * 0.5,
118
- 0.5 + Math.random() * 0.5),
119
- shininess: 30
120
- });
121
- const building = new THREE.Mesh(buildingGeo, buildingMat);
122
-
123
- building.position.copy(position);
124
- building.position.y = height / 2;
125
- building.rotation.y = Math.random() * Math.PI;
126
- scene.add(building);
127
-
128
- position.add(direction.clone().multiplyScalar(height));
129
- buildingCount++;
130
  break;
131
-
132
  case '+':
133
  direction.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.angle);
134
  break;
135
-
136
  case '-':
137
  direction.applyAxisAngle(new THREE.Vector3(0, 0, 1), -this.angle);
138
  break;
139
-
140
  case '[':
141
- stack.push({
142
- position: position.clone(),
143
- direction: direction.clone()
144
- });
145
  break;
146
-
147
  case ']':
148
- if (stack.length > 0) {
149
- const state = stack.pop();
150
- position = state.position;
151
- direction = state.direction;
152
- }
153
  break;
154
  }
155
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
- document.getElementById('building-count').textContent = buildingCount;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  document.getElementById('generation').textContent = this.generation;
159
  }
160
  }
161
 
162
- // Three.js setup
163
  let scene, camera, renderer, controls;
164
  let city;
165
 
166
  function init() {
167
  const container = document.body;
168
-
169
- // Scene
170
  scene = new THREE.Scene();
171
  scene.background = new THREE.Color(0x87CEEB);
172
 
173
- // Camera
174
- camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
175
- camera.position.set(0, 20, 30);
176
 
177
- // Renderer
178
  renderer = new THREE.WebGLRenderer({ antialias: true });
179
- renderer.setSize(window.innerWidth, window.innerHeight);
180
  container.appendChild(renderer.domElement);
181
 
182
  // Lights
@@ -185,38 +226,60 @@ html_code = """
185
  sun.position.set(50, 50, 50);
186
  scene.add(sun);
187
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  // Controls
189
  controls = new THREE.OrbitControls(camera, renderer.domElement);
190
  controls.enableDamping = true;
191
  controls.target.set(0, 0, 0);
192
 
193
  // Initialize city
194
- city = new CityLGrammar();
195
- city.buildCity(scene);
196
 
197
  // Events
198
  window.addEventListener('resize', onWindowResize);
199
- document.getElementById('evolve').addEventListener('click', evolveCity);
200
  document.getElementById('reset').addEventListener('click', resetView);
201
 
202
  animate();
203
  }
204
 
205
- function evolveCity() {
206
- city.evolve();
207
- city.buildCity(scene);
208
- }
209
-
210
  function resetView() {
211
- camera.position.set(0, 20, 30);
212
  controls.target.set(0, 0, 0);
213
  controls.update();
214
  }
215
 
216
  function onWindowResize() {
217
- camera.aspect = window.innerWidth / window.innerHeight;
 
 
218
  camera.updateProjectionMatrix();
219
- renderer.setSize(window.innerWidth, window.innerHeight);
220
  }
221
 
222
  function animate() {
@@ -238,20 +301,20 @@ st.sidebar.title("City Evolution Simulator")
238
  st.sidebar.write("""
239
  ## How to Play
240
 
241
- Watch a city evolve through generations using L-System grammar rules.
242
 
243
  ### Controls:
244
- - **Evolve City**: Generate next city generation
245
  - **Reset View**: Return to default view
246
- - **Left-click + drag**: Rotate view
247
- - **Right-click + drag**: Pan view
248
- - **Scroll**: Zoom in/out
249
-
250
- ### Evolution Rules:
251
- - Each generation adds more buildings
252
- - Buildings branch out from existing structures
253
- - Random heights and sizes create variety
254
- - Roads implicitly form between buildings
255
-
256
- Track progress with building count and generation number!
257
  """)
 
4
  st.set_page_config(page_title="City Evolution Simulator", layout="wide")
5
 
6
  st.title("City Evolution Simulator")
7
+ st.write("Watch a 3D city grow with lakes, hills, and evolving blocks")
8
 
9
  html_code = """
10
  <!DOCTYPE html>
 
46
  <button id="reset">Reset View</button>
47
  <div id="stats">
48
  <p>Buildings: <span id="building-count">0</span></p>
49
+ <p>Blocks: <span id="block-count">0</span></p>
50
  <p>Generation: <span id="generation">0</span></p>
51
  </div>
52
  </div>
 
54
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
55
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
56
  <script>
57
+ class BuildingLSystem {
58
  constructor() {
59
  this.axiom = "F";
60
  this.rules = {
61
+ "F": ["F[+F]", "F[-F]", "FF", "F"],
62
  "+": ["+"],
63
  "-": ["-"],
64
  "[": ["["],
65
  "]": ["]"]
66
  };
67
+ this.angle = Math.PI / 6;
 
 
68
  }
69
 
70
+ generate() {
71
+ let result = this.axiom;
72
+ for (let i = 0; i < 2; i++) {
73
+ let newString = "";
74
+ for (let char of result) {
75
+ if (this.rules[char]) {
76
+ const possible = this.rules[char];
77
+ newString += possible[Math.floor(Math.random() * possible.length)];
78
+ } else {
79
+ newString += char;
80
+ }
 
81
  }
82
+ result = newString;
83
  }
84
+ return result;
 
85
  }
86
 
87
+ build(scene, basePos, maxHeight) {
88
+ let height = 0;
 
 
 
89
  const stack = [];
90
+ let position = basePos.clone();
91
  let direction = new THREE.Vector3(0, 1, 0);
 
 
 
 
 
 
 
 
 
92
 
93
+ const structure = new THREE.Group();
94
+ for (let char of this.generate()) {
95
  switch(char) {
96
  case 'F':
97
+ if (height < maxHeight) {
98
+ const width = 0.8 + Math.random() * 0.4;
99
+ const floorHeight = 1 + Math.random() * 2;
100
+ const geo = new THREE.BoxGeometry(width, floorHeight, width);
101
+ const mat = new THREE.MeshPhongMaterial({
102
+ color: new THREE.Color(0.5 + Math.random() * 0.5,
103
+ 0.5 + Math.random() * 0.5,
104
+ 0.5 + Math.random() * 0.5)
105
+ });
106
+ const floor = new THREE.Mesh(geo, mat);
107
+ floor.position.copy(position).add(new THREE.Vector3(0, floorHeight/2, 0));
108
+ structure.add(floor);
109
+ position.y += floorHeight;
110
+ height += floorHeight;
111
+ }
 
 
 
 
 
112
  break;
 
113
  case '+':
114
  direction.applyAxisAngle(new THREE.Vector3(0, 0, 1), this.angle);
115
  break;
 
116
  case '-':
117
  direction.applyAxisAngle(new THREE.Vector3(0, 0, 1), -this.angle);
118
  break;
 
119
  case '[':
120
+ stack.push(position.clone());
 
 
 
121
  break;
 
122
  case ']':
123
+ if (stack.length > 0) position = stack.pop();
 
 
 
 
124
  break;
125
  }
126
  }
127
+ return structure;
128
+ }
129
+ }
130
+
131
+ class CitySimulator {
132
+ constructor() {
133
+ this.blocks = [];
134
+ this.blockSize = 10; // 10x10 units per block
135
+ this.maxBuildingsPerBlock = 5;
136
+ this.generation = 0;
137
+ this.lakeCenters = [
138
+ new THREE.Vector2(20, 20),
139
+ new THREE.Vector2(-30, 10)
140
+ ];
141
+ }
142
+
143
+ addBlock(scene, x, z) {
144
+ const block = {
145
+ position: new THREE.Vector2(x, z),
146
+ buildings: [],
147
+ maxHeight: this.isWaterfront(x, z) ? 15 : 8
148
+ };
149
+ this.blocks.push(block);
150
+ this.evolveBlock(scene, block, true);
151
+ }
152
 
153
+ isWaterfront(x, z) {
154
+ const pos = new THREE.Vector2(x, z);
155
+ return this.lakeCenters.some(center =>
156
+ pos.distanceTo(center) < 15 && pos.distanceTo(center) > 5);
157
+ }
158
+
159
+ evolveBlock(scene, block, initial = false) {
160
+ if (block.buildings.length < this.maxBuildingsPerBlock) {
161
+ const lsystem = new BuildingLSystem();
162
+ const gridX = Math.floor(Math.random() * 3) - 1;
163
+ const gridZ = Math.floor(Math.random() * 3) - 1;
164
+ const basePos = new THREE.Vector3(
165
+ block.position.x + gridX * 2,
166
+ this.getTerrainHeight(block.position.x, block.position.y),
167
+ block.position.y + gridZ * 2
168
+ );
169
+
170
+ const building = lsystem.build(scene, basePos, block.maxHeight);
171
+ if (this.isWaterfront(block.position.x, block.position.y)) {
172
+ building.scale.set(1.5, 2, 1.5); // Lavish waterfront buildings
173
+ }
174
+ scene.add(building);
175
+ block.buildings.push(building);
176
+ }
177
+ }
178
+
179
+ evolve() {
180
+ this.generation++;
181
+ if (this.blocks.length < 20) {
182
+ const x = (Math.random() - 0.5) * 140;
183
+ const z = (Math.random() - 0.5) * 80;
184
+ if (!this.isInLake(x, z)) this.addBlock(scene, x, z);
185
+ }
186
+ this.blocks.forEach(block => this.evolveBlock(scene, block));
187
+ this.updateStats();
188
+ }
189
+
190
+ getTerrainHeight(x, z) {
191
+ return Math.sin(x * 0.05) * Math.cos(z * 0.05) * 5;
192
+ }
193
+
194
+ isInLake(x, z) {
195
+ const pos = new THREE.Vector2(x, z);
196
+ return this.lakeCenters.some(center => pos.distanceTo(center) < 10);
197
+ }
198
+
199
+ updateStats() {
200
+ const totalBuildings = this.blocks.reduce((sum, block) => sum + block.buildings.length, 0);
201
+ document.getElementById('building-count').textContent = totalBuildings;
202
+ document.getElementById('block-count').textContent = this.blocks.length;
203
  document.getElementById('generation').textContent = this.generation;
204
  }
205
  }
206
 
 
207
  let scene, camera, renderer, controls;
208
  let city;
209
 
210
  function init() {
211
  const container = document.body;
 
 
212
  scene = new THREE.Scene();
213
  scene.background = new THREE.Color(0x87CEEB);
214
 
215
+ // Camera with 16:9 aspect ratio
216
+ camera = new THREE.PerspectiveCamera(75, 16 / 9, 0.1, 1000);
217
+ camera.position.set(0, 50, 80);
218
 
 
219
  renderer = new THREE.WebGLRenderer({ antialias: true });
220
+ renderer.setSize(window.innerWidth, window.innerHeight * (9/16));
221
  container.appendChild(renderer.domElement);
222
 
223
  // Lights
 
226
  sun.position.set(50, 50, 50);
227
  scene.add(sun);
228
 
229
+ // Terrain and Lakes
230
+ const groundGeo = new THREE.PlaneGeometry(160, 90, 32, 32);
231
+ const groundMat = new THREE.MeshPhongMaterial({ color: 0x4a7043 });
232
+ const ground = new THREE.Mesh(groundGeo, groundMat);
233
+ ground.rotation.x = -Math.PI / 2;
234
+ ground.position.y = -0.1;
235
+ scene.add(ground);
236
+
237
+ // Add lakes
238
+ const lakeGeo = new THREE.CircleGeometry(10, 32);
239
+ const lakeMat = new THREE.MeshPhongMaterial({ color: 0x4682b4 });
240
+ city.lakeCenters.forEach(center => {
241
+ const lake = new THREE.Mesh(lakeGeo, lakeMat);
242
+ lake.rotation.x = -Math.PI / 2;
243
+ lake.position.set(center.x, 0, center.y);
244
+ scene.add(lake);
245
+ });
246
+
247
+ // Add bridges
248
+ const bridgeGeo = new THREE.BoxGeometry(5, 0.2, 15);
249
+ const bridgeMat = new THREE.MeshPhongMaterial({ color: 0x808080 });
250
+ const bridge1 = new THREE.Mesh(bridgeGeo, bridgeMat);
251
+ bridge1.position.set(15, 0.2, 20);
252
+ scene.add(bridge1);
253
+
254
  // Controls
255
  controls = new THREE.OrbitControls(camera, renderer.domElement);
256
  controls.enableDamping = true;
257
  controls.target.set(0, 0, 0);
258
 
259
  // Initialize city
260
+ city = new CitySimulator();
261
+ city.addBlock(scene, 0, 0);
262
 
263
  // Events
264
  window.addEventListener('resize', onWindowResize);
265
+ document.getElementById('evolve').addEventListener('click', () => city.evolve());
266
  document.getElementById('reset').addEventListener('click', resetView);
267
 
268
  animate();
269
  }
270
 
 
 
 
 
 
271
  function resetView() {
272
+ camera.position.set(0, 50, 80);
273
  controls.target.set(0, 0, 0);
274
  controls.update();
275
  }
276
 
277
  function onWindowResize() {
278
+ const width = window.innerWidth;
279
+ const height = width * (9/16);
280
+ camera.aspect = 16 / 9;
281
  camera.updateProjectionMatrix();
282
+ renderer.setSize(width, height);
283
  }
284
 
285
  function animate() {
 
301
  st.sidebar.write("""
302
  ## How to Play
303
 
304
+ Watch a 3D city evolve with lakes, hills, and building blocks.
305
 
306
  ### Controls:
307
+ - **Evolve City**: Grow the city
308
  - **Reset View**: Return to default view
309
+ - **Left-click + drag**: Rotate
310
+ - **Right-click + drag**: Pan
311
+ - **Scroll**: Zoom
312
+
313
+ ### Features:
314
+ - 16:9 play area
315
+ - Blocks (10x10 units) with up to 5 buildings
316
+ - Buildings evolve floor-by-floor using L-systems
317
+ - Terrain with hills and lakes
318
+ - Waterfront properties grow larger
319
+ - Bridges connect land masses
320
  """)