///|
/// compile_cylinder emits a solid right circular cylinder as three analytic
/// faces: one `CYLINDRICAL_SURFACE` band and two circular `PLANE` caps.
///
/// Topology: the lateral surface is cut along the seam at +X. The seam edge
/// runs from V0 = (r, 0, 0) to V1 = (r, 0, h); each cap edge is a closed
/// `EDGE_CURVE` that starts and ends at its own vertex. The lateral loop
/// traverses the seam up, the top circle backwards, the seam down, and the
/// bottom circle forwards, so every edge is used exactly twice with opposite
/// sense (the seam twice within the same face, which is the ISO 10303 seam
/// convention).
pub fn compile_cylinder(
  ctx : StepContext,
  node : SceneNode,
  cylinder : Cylinder,
  reverse~ : Bool = false,
  parent~ : Transform? = None,
) -> Array[@step.Ref] {
  let r = cylinder.diameter / 2.0
  let h = cylinder.height
  if r <= 0.0 || h <= 0.0 {
    return []
  }
  let xform = node_transform(node, parent)
  let radius = @step.fmt_num(r)

  // Surface frames, transformed with the node.
  let origin_b = xform.apply((0.0, 0.0, 0.0))
  let origin_t = xform.apply((0.0, 0.0, h))
  let origin_b_ref = ctx.cartesian_point(origin_b.0, origin_b.1, origin_b.2)
  let origin_t_ref = ctx.cartesian_point(origin_t.0, origin_t.1, origin_t.2)
  let up = xform.apply_dir((0.0, 0.0, 1.0))
  let down = xform.apply_dir((0.0, 0.0, -1.0))
  let right = xform.apply_dir((1.0, 0.0, 0.0))
  let up_ref = ctx.direction(up.0, up.1, up.2)
  let down_ref = ctx.direction(down.0, down.1, down.2)
  let right_ref = ctx.direction(right.0, right.1, right.2)

  // The lateral surface and both circular edges follow +Z. The bottom cap's
  // plane points outward (-Z) so its placement, not a flipped same_sense,
  // defines the face normal.
  let a2p_bottom = ctx.axis2_placement_3d(origin_b_ref, up_ref, right_ref)
  let a2p_top = ctx.axis2_placement_3d(origin_t_ref, up_ref, right_ref)
  let a2p_bottom_plane = ctx.axis2_placement_3d(
    origin_b_ref, down_ref, right_ref,
  )

  // Analytic geometry.
  let circle_bottom_ref = ctx.add(
    @repository.any_entity_circle(@geometry.Circle::new("", a2p_bottom, radius)),
  )
  let circle_top_ref = ctx.add(
    @repository.any_entity_circle(@geometry.Circle::new("", a2p_top, radius)),
  )
  let lateral_surface_ref = ctx.add(
    @repository.any_entity_cylindrical_surface(
      @geometry.CylindricalSurface::new("", a2p_bottom, radius),
    ),
  )
  let bottom_plane_ref = ctx.add(
    @repository.any_entity_plane(@geometry.Plane::new("", a2p_bottom_plane)),
  )
  let top_plane_ref = ctx.add(
    @repository.any_entity_plane(@geometry.Plane::new("", a2p_top)),
  )

  // Seam vertices and geometry.
  let v0 = xform.apply((r, 0.0, 0.0))
  let v1 = xform.apply((r, 0.0, h))
  let cp0 = ctx.cartesian_point(v0.0, v0.1, v0.2)
  let cp1 = ctx.cartesian_point(v1.0, v1.1, v1.2)
  let vp0 = ctx.vertex_point(cp0)
  let vp1 = ctx.vertex_point(cp1)
  let seam_line_ref = ctx.line(cp0, ctx.vector(up_ref, 1.0))

  // Edges.
  let seam_ref = ctx.edge_curve(vp0, vp1, seam_line_ref, true)
  let bottom_edge_ref = ctx.edge_curve(vp0, vp0, circle_bottom_ref, true)
  let top_edge_ref = ctx.edge_curve(vp1, vp1, circle_top_ref, true)

  // Bounds.
  let lateral_loop_ref = ctx.edge_loop([
    ctx.oriented_edge(seam_ref, true),
    ctx.oriented_edge(top_edge_ref, false),
    ctx.oriented_edge(seam_ref, false),
    ctx.oriented_edge(bottom_edge_ref, true),
  ])
  let bottom_loop_ref = ctx.edge_loop([
    ctx.oriented_edge(bottom_edge_ref, false),
  ])
  let top_loop_ref = ctx.edge_loop([ctx.oriented_edge(top_edge_ref, true)])
  let lateral_bound_ref = ctx.face_outer_bound(lateral_loop_ref, true)
  let bottom_bound_ref = ctx.face_outer_bound(bottom_loop_ref, true)
  let top_bound_ref = ctx.face_outer_bound(top_loop_ref, true)

  // Faces.
  [
    ctx.advanced_face([lateral_bound_ref], lateral_surface_ref, !reverse),
    ctx.advanced_face([bottom_bound_ref], bottom_plane_ref, !reverse),
    ctx.advanced_face([top_bound_ref], top_plane_ref, !reverse),
  ]
}