From cf73da29557930c6bfd2e4823920593ba8a6dae6 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 15 Dec 2021 20:11:02 -0500 Subject: [PATCH] Gnarling watch towers. --- world/src/site2/gen.rs | 12 ++ world/src/site2/plot/gnarling.rs | 207 ++++++++++++++++++++++++------- 2 files changed, 172 insertions(+), 47 deletions(-) diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs index 0f6787e76b..c9790e72aa 100644 --- a/world/src/site2/gen.rs +++ b/world/src/site2/gen.rs @@ -114,6 +114,18 @@ impl Primitive { pub fn repeat(a: impl Into>, offset: Vec3, count: i32) -> Self { Self::Repeat(a.into(), offset, count) } + + pub fn cylinder(origin: Vec3, radius: f32, height: f32) -> Self { + let min = origin - Vec2::broadcast(radius.round() as i32); + let max = origin + Vec2::broadcast(radius.round() as i32).with_z(height.round() as i32); + Primitive::Cylinder(Aabb { min, max }) + } + + pub fn sphere(origin: Vec3, radius: f32) -> Self { + let min = origin - Vec3::broadcast(radius.round() as i32); + let max = origin + Vec3::broadcast(radius.round() as i32); + Primitive::Sphere(Aabb { min, max }) + } } #[derive(Clone)] diff --git a/world/src/site2/plot/gnarling.rs b/world/src/site2/plot/gnarling.rs index e4b866a4d9..b12160943d 100644 --- a/world/src/site2/plot/gnarling.rs +++ b/world/src/site2/plot/gnarling.rs @@ -9,7 +9,10 @@ pub struct GnarlingFortification { seed: u32, origin: Vec2, radius: i32, - ordered_wall_points: Vec>, + wall_radius: i32, + // Vec2 is relative position of wall relative to site origin, bool indicates whether it is a + // corner, and thus whether a tower gets constructed + ordered_wall_points: Vec<(Vec2, bool)>, } impl GnarlingFortification { @@ -18,27 +21,31 @@ impl GnarlingFortification { let seed = rng.gen(); let origin = wpos; - let radius = { + let wall_radius = { let unit_size = rng.gen_range(10..20); let num_units = rng.gen_range(5..10); let variation = rng.gen_range(0..50); unit_size * num_units + variation }; - let num_points = (radius / 15).max(4); + let radius = wall_radius + 25; + + let num_points = (wall_radius / 15).max(4); let ordered_wall_points = (0..num_points) .into_iter() .map(|a| { let angle = a as f32 / num_points as f32 * core::f32::consts::TAU; - Vec2::new(angle.cos(), angle.sin()).map(|a| (a * radius as f32) as i32) + Vec2::new(angle.cos(), angle.sin()).map(|a| (a * wall_radius as f32) as i32) }) .map(|point| { point.map(|a| { - let variation = radius / 5; + let variation = wall_radius / 5; a + rng.gen_range(-variation..=variation) }) }) .collect::>(); + // This adds additional points for the wall on the line between two points, + // allowing the wall to better handle slopes let ordered_wall_points = ordered_wall_points .iter() .enumerate() @@ -50,7 +57,7 @@ impl GnarlingFortification { }; let mid_point_1 = point + (next_point - point) / 3; let mid_point_2 = point + (next_point - point) * 2 / 3; - [*point, mid_point_1, mid_point_2] + [(*point, true), (mid_point_1, false), (mid_point_2, false)] }) .collect::>(); @@ -59,6 +66,7 @@ impl GnarlingFortification { seed, origin, radius, + wall_radius, ordered_wall_points, } } @@ -71,9 +79,9 @@ impl GnarlingFortification { impl Structure for GnarlingFortification { fn render(&self, _site: &Site, land: &Land, painter: &Painter) { // Create outer wall - for (i, point) in self.ordered_wall_points.iter().enumerate() { + for (i, (point, _is_tower)) in self.ordered_wall_points.iter().enumerate() { // Other point of wall segment - let next_point = if let Some(point) = self.ordered_wall_points.get(i + 1) { + let (next_point, _is_tower) = if let Some(point) = self.ordered_wall_points.get(i + 1) { *point } else { self.ordered_wall_points[0] @@ -91,35 +99,34 @@ impl Structure for GnarlingFortification { .as_() .with_z(land.get_alt_approx(end_wpos) - wall_depth); - let wall_base_segment = LineSegment3 { start, end }; let wall_base_thickness = 3.0; let wall_base_height = 3.0; - painter.fill( - painter.prim(Primitive::SegmentPrism( - wall_base_segment, + painter + .segment_prism( + start, + end, wall_base_thickness, wall_base_height + wall_depth as f32, - )), - Fill::Block(Block::new(BlockKind::Wood, Rgb::new(55, 25, 8))), - ); + ) + .fill(Fill::Block(Block::new( + BlockKind::Wood, + Rgb::new(55, 25, 8), + ))); // Middle of wall let start = start_wpos.as_().with_z(land.get_alt_approx(start_wpos)); let end = end_wpos.as_().with_z(land.get_alt_approx(end_wpos)); - let wall_mid_segment = LineSegment3 { start, end }; let wall_mid_thickness = 1.0; let wall_mid_height = 5.0 + wall_base_height; - painter.fill( - painter.prim(Primitive::SegmentPrism( - wall_mid_segment, - wall_mid_thickness, - wall_mid_height, - )), - Fill::Block(Block::new(BlockKind::Wood, Rgb::new(55, 25, 8))), - ); + painter + .segment_prism(start, end, wall_mid_thickness, wall_mid_height) + .fill(Fill::Block(Block::new( + BlockKind::Wood, + Rgb::new(55, 25, 8), + ))); // Top of wall let start = start_wpos @@ -129,47 +136,153 @@ impl Structure for GnarlingFortification { .as_() .with_z(land.get_alt_approx(end_wpos) + wall_mid_height); - let wall_top_segment = LineSegment3 { start, end }; let wall_top_thickness = 2.0; let wall_top_height = 1.0; - painter.fill( - painter.prim(Primitive::SegmentPrism( - wall_top_segment, - wall_top_thickness, - wall_top_height, - )), - Fill::Block(Block::new(BlockKind::Wood, Rgb::new(55, 25, 8))), - ); + painter + .segment_prism(start, end, wall_top_thickness, wall_top_height) + .fill(Fill::Block(Block::new( + BlockKind::Wood, + Rgb::new(55, 25, 8), + ))); // Wall parapets + let parapet_z_offset = 1.0; + let start = Vec3::new( - point.x as f32 * (self.radius as f32 + 1.0) / (self.radius as f32) + point.x as f32 * (self.wall_radius as f32 + 1.0) / (self.wall_radius as f32) + self.origin.x as f32, - point.y as f32 * (self.radius as f32 + 1.0) / (self.radius as f32) + point.y as f32 * (self.wall_radius as f32 + 1.0) / (self.wall_radius as f32) + self.origin.y as f32, - land.get_alt_approx(start_wpos) + wall_mid_height + wall_top_height - 1.0, + land.get_alt_approx(start_wpos) + wall_mid_height + wall_top_height + - parapet_z_offset, ); let end = Vec3::new( - next_point.x as f32 * (self.radius as f32 + 1.0) / (self.radius as f32) + next_point.x as f32 * (self.wall_radius as f32 + 1.0) / (self.wall_radius as f32) + self.origin.x as f32, - next_point.y as f32 * (self.radius as f32 + 1.0) / (self.radius as f32) + next_point.y as f32 * (self.wall_radius as f32 + 1.0) / (self.wall_radius as f32) + self.origin.y as f32, - land.get_alt_approx(end_wpos) + wall_mid_height + wall_top_height - 1.0, + land.get_alt_approx(end_wpos) + wall_mid_height + wall_top_height + - parapet_z_offset, ); - let wall_par_segment = LineSegment3 { start, end }; let wall_par_thickness = tweak!(0.8); let wall_par_height = 1.0; - painter.fill( - painter.prim(Primitive::SegmentPrism( - wall_par_segment, + painter + .segment_prism( + start, + end, wall_par_thickness, - wall_par_height + 1.0, - )), - Fill::Block(Block::new(BlockKind::Wood, Rgb::new(55, 25, 8))), - ); + wall_par_height + parapet_z_offset as f32, + ) + .fill(Fill::Block(Block::new( + BlockKind::Wood, + Rgb::new(55, 25, 8), + ))); } + + // Create towers + self.ordered_wall_points + .iter() + .filter_map(|(point, is_tower)| is_tower.then_some(point)) + .for_each(|point| { + let wpos = point + self.origin; + + // Tower base + let tower_depth = 3; + let tower_base_pos = wpos.with_z(land.get_alt_approx(wpos) as i32 - tower_depth); + let tower_radius = 5.; + let tower_height = 20.0; + + painter + .prim(Primitive::cylinder( + tower_base_pos, + tower_radius, + tower_depth as f32 + tower_height, + )) + .fill(Fill::Block(Block::new( + BlockKind::Wood, + Rgb::new(55, 25, 8), + ))); + + // Tower cylinder + let tower_floor_pos = wpos.with_z(land.get_alt_approx(wpos) as i32); + + painter + .prim(Primitive::cylinder( + tower_floor_pos, + tower_radius - 1.0, + tower_height, + )) + .fill(Fill::Block(Block::empty())); + + // Tower top floor + let top_floor_z = (land.get_alt_approx(wpos) + tower_height - 2.0) as i32; + let tower_top_floor_pos = wpos.with_z(top_floor_z); + + painter + .prim(Primitive::cylinder(tower_top_floor_pos, tower_radius, 1.0)) + .fill(Fill::Block(Block::new( + BlockKind::Wood, + Rgb::new(55, 25, 8), + ))); + + // Tower roof poles + let roof_pole_height = 5; + let relative_pole_positions = [ + Vec2::new(-4, -4), + Vec2::new(-4, 3), + Vec2::new(3, -4), + Vec2::new(3, 3), + ]; + relative_pole_positions + .iter() + .map(|rpos| wpos + rpos) + .for_each(|pole_pos| { + painter + .line( + pole_pos.with_z(top_floor_z), + pole_pos.with_z(top_floor_z + roof_pole_height), + 1., + ) + .fill(Fill::Block(Block::new( + BlockKind::Wood, + Rgb::new(55, 25, 8), + ))); + }); + + // Tower roof + let roof_sphere_radius = 10; + let roof_radius = tower_radius + 1.0; + let roof_height = 3; + + let roof_cyl = painter.prim(Primitive::cylinder( + wpos.with_z(top_floor_z + roof_pole_height), + roof_radius, + roof_height as f32, + )); + + painter + .prim(Primitive::sphere( + wpos.with_z( + top_floor_z + roof_pole_height + roof_height - roof_sphere_radius, + ), + roof_sphere_radius as f32, + )) + .intersect(roof_cyl) + .fill(Fill::Block(Block::new( + BlockKind::Wood, + Rgb::new(55, 25, 8), + ))); + // TODO: There is probably a better way of getting intersection + // of 2 primitives than this? let roof_sphere = + // painter.prim(Primitive::sphere(wpos.with_z(top_floor_z + + // roof_pole_height + roof_height - roof_sphere_radius), + // roof_sphere_radius as f32)); painter + // .prim(roof_sphere.intersect(roof_cyl)) + // .fill(Fill::Block(Block::new(BlockKind::Wood, + // Rgb::new(55, 25, 8)))); + }) } }