Gnarling watch towers.

This commit is contained in:
Sam 2021-12-15 20:11:02 -05:00
parent 90cedfc95d
commit cf73da2955
2 changed files with 172 additions and 47 deletions

View File

@ -114,6 +114,18 @@ impl Primitive {
pub fn repeat(a: impl Into<Id<Primitive>>, offset: Vec3<i32>, count: i32) -> Self {
Self::Repeat(a.into(), offset, count)
}
pub fn cylinder(origin: Vec3<i32>, 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<i32>, 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)]

View File

@ -9,7 +9,10 @@ pub struct GnarlingFortification {
seed: u32,
origin: Vec2<i32>,
radius: i32,
ordered_wall_points: Vec<Vec2<i32>>,
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<i32>, 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::<Vec<_>>();
// 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::<Vec<_>>();
@ -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))));
})
}
}