Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/large_scenes/bevy_city/src/generate_city.rs
9730 views
1
use bevy::prelude::*;
2
use noise::{NoiseFn, OpenSimplex};
3
use rand::{rngs::SmallRng, RngExt, SeedableRng};
4
5
use crate::{assets::CityAssets, Car, Road};
6
7
#[derive(Component)]
8
pub struct CityRoot;
9
10
/// Spawns a grid of city blocks
11
///
12
/// For simplicity we spawn the roads and buildings in this pattern
13
///
14
/// X-------
15
/// | B B B
16
/// | B B B
17
///
18
/// X = crossroad, B = buildings
19
///
20
/// This way we can easily tile each city block
21
/// Each city block is 5.5 units x 4.0 units.
22
///
23
/// Every asset gets spawned relative to the crossroad position
24
pub fn spawn_city(commands: &mut Commands, assets: &CityAssets, seed: u64, size: u32) {
25
let mut rng = SmallRng::seed_from_u64(seed);
26
let noise = OpenSimplex::new(rng.random());
27
let noise_scale = 0.025;
28
29
commands
30
.spawn((CityRoot, Transform::default(), Visibility::default()))
31
.with_children(|commands| {
32
let half_size = size as i32 / 2;
33
for x in -half_size..half_size {
34
for z in -half_size..half_size {
35
// scale the position to match the city block size
36
let x = x as f32 * 5.5;
37
let z = z as f32 * 4.0;
38
let offset = Vec3::new(x, 0.0, z);
39
40
spawn_roads_and_cars(commands, assets, &mut rng, offset);
41
42
let density = noise.get([
43
offset.x as f64 * noise_scale,
44
offset.z as f64 * noise_scale,
45
0.0,
46
]) * 0.5
47
+ 0.5;
48
49
let forest = 0.45;
50
let low_density = 0.6;
51
let medium_density = 0.7;
52
53
let ground_tile_scale = Vec3::new(4.5, 1.0, 3.0);
54
commands.spawn((
55
Mesh3d(assets.ground_tile.0.clone()),
56
if density < low_density {
57
MeshMaterial3d(assets.ground_tile.2.clone())
58
} else {
59
MeshMaterial3d(assets.ground_tile.1.clone())
60
},
61
Transform::from_translation(
62
Vec3::new(0.5, -0.5005, 0.5) + ground_tile_scale / 2.0 + offset,
63
)
64
.with_scale(ground_tile_scale),
65
));
66
67
if density < forest {
68
spawn_forest(commands, assets, &mut rng, offset);
69
} else if density < low_density {
70
spawn_low_density(commands, assets, &mut rng, offset);
71
} else if density < medium_density {
72
spawn_medium_density(commands, assets, &mut rng, offset);
73
} else {
74
spawn_high_density(commands, assets, &mut rng, offset);
75
}
76
}
77
}
78
});
79
}
80
81
fn spawn_roads_and_cars<R: RngExt>(
82
commands: &mut ChildSpawnerCommands,
83
assets: &CityAssets,
84
rng: &mut R,
85
offset: Vec3,
86
) {
87
let x = offset.x;
88
let z = offset.z;
89
90
commands.spawn((
91
SceneRoot(assets.crossroad.clone()),
92
Transform::from_xyz(x, 0.0, z),
93
));
94
95
let max_car_density = 0.4;
96
97
// When spawning roads we rotate and stretch a single road asset instead of spawning multiple
98
// road segments
99
100
// NOTE most of the magic numbers were hand tweaked for something that looks visually nice
101
102
// horizontal road
103
let car_count = 9;
104
commands
105
.spawn((
106
Transform::from_translation(offset),
107
Visibility::default(),
108
Road {
109
start: Vec3::new(0.75, 0.0, 0.0),
110
end: Vec3::new(0.75 + (0.5 * car_count as f32), 0.0, 0.0),
111
},
112
))
113
.with_children(|commands| {
114
commands.spawn((
115
SceneRoot(assets.road_straight.clone()),
116
Transform::from_translation(Vec3::new(2.75, 0.0, 0.0))
117
.with_scale(Vec3::new(4.5, 1.0, 1.0)),
118
));
119
120
for i in 0..car_count {
121
let car_pos = Vec3::new(0.0, 0.0, 0.75 + i as f32 * 0.5);
122
123
if rng.random::<f32>() < max_car_density {
124
commands.spawn((
125
SceneRoot(assets.get_random_car(rng)),
126
Transform::from_translation(car_pos + Vec3::new(0.0, 0.0, -0.15))
127
.with_scale(Vec3::splat(0.15))
128
.with_rotation(Quat::from_axis_angle(
129
Vec3::Y,
130
3.0 * std::f32::consts::FRAC_PI_2,
131
)),
132
Car {
133
distance_traveled: i as f32 * 0.5,
134
dir: -1.0,
135
offset: Vec3::new(4.25, 0.0, -0.15),
136
},
137
));
138
}
139
140
if rng.random::<f32>() < max_car_density {
141
commands.spawn((
142
SceneRoot(assets.get_random_car(rng)),
143
Transform::from_translation(car_pos + Vec3::new(0.0, 0.0, 0.15))
144
.with_scale(Vec3::splat(0.15))
145
.with_rotation(Quat::from_axis_angle(
146
Vec3::Y,
147
std::f32::consts::FRAC_PI_2,
148
)),
149
Car {
150
distance_traveled: i as f32 * 0.5,
151
dir: 1.0,
152
offset: Vec3::new(-0.25, 0.0, 0.15),
153
},
154
));
155
}
156
}
157
});
158
159
// vertical road
160
let car_count = 6;
161
commands
162
.spawn((
163
Transform::from_translation(offset),
164
Visibility::default(),
165
Road {
166
start: Vec3::new(0.0, 0.0, 0.75),
167
end: Vec3::new(0.0, 0.0, 0.75 + (0.5 * car_count as f32)),
168
},
169
))
170
.with_children(|commands| {
171
commands.spawn((
172
SceneRoot(assets.road_straight.clone()),
173
Transform::from_translation(Vec3::new(0.0, 0.0, 2.0))
174
.with_scale(Vec3::new(3.0, 1.0, 1.0))
175
.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::FRAC_PI_2)),
176
));
177
178
for i in 0..car_count {
179
let car_pos = Vec3::new(0.0, 0.0, 0.75 + i as f32 * 0.5);
180
181
if rng.random::<f32>() < max_car_density {
182
commands.spawn((
183
SceneRoot(assets.get_random_car(rng)),
184
Transform::from_translation(car_pos + Vec3::new(0.15, 0.0, 0.0))
185
.with_scale(Vec3::splat(0.15)),
186
Car {
187
distance_traveled: i as f32 * 0.5,
188
dir: 1.0,
189
offset: Vec3::new(-0.15, 0.0, -0.25),
190
},
191
));
192
}
193
194
if rng.random::<f32>() < max_car_density {
195
commands.spawn((
196
SceneRoot(assets.get_random_car(rng)),
197
Transform::from_translation(car_pos + Vec3::new(-0.15, 0.0, 0.0))
198
.with_scale(Vec3::splat(0.15))
199
.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::PI)),
200
Car {
201
distance_traveled: i as f32 * 0.5,
202
dir: -1.0,
203
offset: Vec3::new(0.15, 0.0, 2.75),
204
},
205
));
206
}
207
}
208
});
209
}
210
211
fn spawn_low_density<R: RngExt>(
212
commands: &mut ChildSpawnerCommands,
213
assets: &CityAssets,
214
rng: &mut R,
215
offset: Vec3,
216
) {
217
for x in 1..=2 {
218
let x_factor = 1.8;
219
commands.spawn((
220
assets.low_density.get_random_building(rng),
221
Transform::from_translation(Vec3::new(x as f32 * x_factor, 0.0, 1.25) + offset),
222
));
223
commands.spawn((
224
assets.low_density.get_random_building(rng),
225
Transform::from_translation(Vec3::new(x as f32 * x_factor, 0.0, 2.75) + offset)
226
.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::PI)),
227
));
228
}
229
for i in 0..=6 {
230
commands.spawn((
231
SceneRoot(assets.fence.clone()),
232
Transform::from_translation(Vec3::new(2.75, 0.0, 0.75 + i as f32 * 0.4) + offset)
233
.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::FRAC_PI_2)),
234
));
235
}
236
for z in 0..=8 {
237
commands.spawn((
238
SceneRoot(assets.tree_small.clone()),
239
Transform::from_translation(Vec3::new(0.75, 0.0, 0.75 + z as f32 * 0.3) + offset),
240
));
241
commands.spawn((
242
SceneRoot(assets.tree_small.clone()),
243
Transform::from_translation(Vec3::new(4.75, 0.0, 0.75 + z as f32 * 0.3) + offset),
244
));
245
}
246
}
247
248
fn spawn_medium_density<R: RngExt>(
249
commands: &mut ChildSpawnerCommands,
250
assets: &CityAssets,
251
rng: &mut R,
252
offset: Vec3,
253
) {
254
let x_factor = 0.9;
255
for x in 1..=5 {
256
commands.spawn((
257
assets.medium_density.get_random_building(rng),
258
Transform::from_translation(Vec3::new(x as f32 * x_factor, 0.0, 1.0) + offset),
259
));
260
261
for tree_x in 0..=1 {
262
let tree_x = tree_x as f32 * 0.5;
263
if x == 5 && tree_x == 0.5 {
264
break;
265
}
266
commands.spawn((
267
SceneRoot(assets.tree_large.clone()),
268
Transform::from_translation(
269
Vec3::new(tree_x + x as f32 * x_factor, 0.0, 1.75) + offset,
270
),
271
));
272
commands.spawn((
273
SceneRoot(assets.tree_large.clone()),
274
Transform::from_translation(
275
Vec3::new(tree_x + x as f32 * x_factor, 0.0, 2.25) + offset,
276
),
277
));
278
}
279
280
commands.spawn((
281
assets.medium_density.get_random_building(rng),
282
Transform::from_translation(Vec3::new(x as f32 * x_factor, 0.0, 3.0) + offset)
283
.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::PI)),
284
));
285
}
286
287
for x in 0..=10 {
288
commands.spawn((
289
SceneRoot(assets.path_stones_long.clone()),
290
Transform::from_translation(Vec3::new(0.75 + (x as f32 * 0.4), 0.02, 2.0) + offset)
291
.with_scale(Vec3::new(1.0, 2.0, 1.0))
292
.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::FRAC_PI_2)),
293
));
294
commands.spawn((
295
SceneRoot(assets.fence.clone()),
296
Transform::from_translation(Vec3::new(0.75 + (x as f32 * 0.4), 0.02, 1.85) + offset),
297
));
298
commands.spawn((
299
SceneRoot(assets.fence.clone()),
300
Transform::from_translation(Vec3::new(0.75 + (x as f32 * 0.4), 0.02, 2.15) + offset),
301
));
302
}
303
}
304
305
fn spawn_high_density<R: RngExt>(
306
commands: &mut ChildSpawnerCommands,
307
assets: &CityAssets,
308
rng: &mut R,
309
offset: Vec3,
310
) {
311
for x in 0..3 {
312
let x = x as f32;
313
commands.spawn((
314
assets.high_density.get_random_building(rng),
315
Transform::from_translation(Vec3::new(1.25 + x * 1.5, 0.0, 1.25) + offset),
316
));
317
commands.spawn((
318
assets.high_density.get_random_building(rng),
319
Transform::from_translation(Vec3::new(1.25 + x * 1.5, 0.0, 2.75) + offset)
320
.with_rotation(Quat::from_axis_angle(Vec3::Y, std::f32::consts::PI)),
321
));
322
}
323
}
324
325
fn spawn_forest<R: RngExt>(
326
commands: &mut ChildSpawnerCommands,
327
assets: &CityAssets,
328
rng: &mut R,
329
offset: Vec3,
330
) {
331
for x in 0..=12 {
332
for z in 0..=8 {
333
let transform = Transform::from_translation(
334
Vec3::new(x as f32, 0.0, z as f32) * Vec3::new(0.325, 0.0, 0.3)
335
+ Vec3::new(0.75, 0.0, 0.85)
336
+ offset,
337
);
338
339
match rng.random_range(0..3) {
340
0 => {}
341
1 => {
342
commands.spawn((SceneRoot(assets.tree_small.clone()), transform));
343
}
344
2 => {
345
commands.spawn((SceneRoot(assets.tree_large.clone()), transform));
346
}
347
_ => {}
348
}
349
}
350
}
351
}
352
353