bem_templates.f90 Source File


This file depends on

sourcefile~~bem_templates.f90~~EfferentGraph sourcefile~bem_templates.f90 bem_templates.f90 sourcefile~bem_kinds.f90 bem_kinds.f90 sourcefile~bem_templates.f90->sourcefile~bem_kinds.f90 sourcefile~bem_mesh.f90 bem_mesh.f90 sourcefile~bem_templates.f90->sourcefile~bem_mesh.f90 sourcefile~bem_types.f90 bem_types.f90 sourcefile~bem_templates.f90->sourcefile~bem_types.f90 sourcefile~bem_mesh.f90->sourcefile~bem_kinds.f90 sourcefile~bem_mesh.f90->sourcefile~bem_types.f90 sourcefile~bem_string_utils.f90 bem_string_utils.f90 sourcefile~bem_mesh.f90->sourcefile~bem_string_utils.f90 sourcefile~bem_types.f90->sourcefile~bem_kinds.f90

Files dependent on this one

sourcefile~~bem_templates.f90~~AfferentGraph sourcefile~bem_templates.f90 bem_templates.f90 sourcefile~bem_app_config_runtime.f90 bem_app_config_runtime.f90 sourcefile~bem_app_config_runtime.f90->sourcefile~bem_templates.f90 sourcefile~bem_app_config.f90 bem_app_config.f90 sourcefile~bem_app_config.f90->sourcefile~bem_app_config_runtime.f90 sourcefile~bem_simulator.f90 bem_simulator.f90 sourcefile~bem_simulator.f90->sourcefile~bem_app_config.f90 sourcefile~main.f90 main.f90 sourcefile~main.f90->sourcefile~bem_app_config.f90 sourcefile~main.f90->sourcefile~bem_simulator.f90 sourcefile~bem_simulator_io.f90 bem_simulator_io.f90 sourcefile~bem_simulator_io.f90->sourcefile~bem_simulator.f90 sourcefile~bem_simulator_loop.f90 bem_simulator_loop.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_simulator.f90 sourcefile~bem_simulator_stats.f90 bem_simulator_stats.f90 sourcefile~bem_simulator_stats.f90->sourcefile~bem_simulator.f90

Source Code

!> 平面/穴あき平面/円板/リング/箱/円柱/球テンプレートから三角形メッシュを生成するユーティリティ。
module bem_templates
  use bem_kinds, only: dp, i32
  use bem_types, only: mesh_type
  use bem_mesh, only: init_mesh
  implicit none
contains

  !> XY平面を `nx*ny` 分割し、各セルを2三角形へ分割したメッシュを生成する。
  !! @param[out] mesh 生成した平面三角形メッシュ。
  !! @param[in] size_x X方向の平面サイズ [m](省略時 1.0)。
  !! @param[in] size_y Y方向の平面サイズ [m](省略時 1.0)。
  !! @param[in] nx X方向分割数(省略時 1)。
  !! @param[in] ny Y方向分割数(省略時 1)。
  !! @param[in] center 平面中心座標 `(x,y,z)` [m](省略時原点)。
  subroutine make_plane(mesh, size_x, size_y, nx, ny, center)
    type(mesh_type), intent(out) :: mesh
    real(dp), intent(in), optional :: size_x, size_y
    integer(i32), intent(in), optional :: nx, ny
    real(dp), intent(in), optional :: center(3)
    real(dp) :: sx, sy, c(3), x0, y0, dx, dy
    integer(i32) :: nx0, ny0, ix, iy, itri, nelem
    real(dp), allocatable :: v0(:, :), v1(:, :), v2(:, :)

    sx = 1.0d0; sy = 1.0d0; nx0 = 1; ny0 = 1; c = 0.0d0
    if (present(size_x)) sx = size_x
    if (present(size_y)) sy = size_y
    if (present(nx)) nx0 = nx
    if (present(ny)) ny0 = ny
    if (present(center)) c = center
    if (nx0 <= 0 .or. ny0 <= 0) error stop "nx and ny must be positive"

    nelem = 2*nx0*ny0
    allocate (v0(3, nelem), v1(3, nelem), v2(3, nelem))
    dx = sx/real(nx0, dp); dy = sy/real(ny0, dp)
    x0 = c(1) - 0.5d0*sx; y0 = c(2) - 0.5d0*sy

    itri = 0
    do ix = 0, nx0 - 1
      do iy = 0, ny0 - 1
        itri = itri + 1
        v0(:, itri) = [x0 + dx*ix, y0 + dy*iy, c(3)]
        v1(:, itri) = [x0 + dx*(ix + 1), y0 + dy*iy, c(3)]
        v2(:, itri) = [x0 + dx*(ix + 1), y0 + dy*(iy + 1), c(3)]
        itri = itri + 1
        v0(:, itri) = [x0 + dx*ix, y0 + dy*iy, c(3)]
        v1(:, itri) = [x0 + dx*(ix + 1), y0 + dy*(iy + 1), c(3)]
        v2(:, itri) = [x0 + dx*ix, y0 + dy*(iy + 1), c(3)]
      end do
    end do
    call init_mesh(mesh, v0, v1, v2)
  end subroutine make_plane

  !> XY平面上の円板を極座標分割し、外周へ向かって三角形化したメッシュを生成する。
  !! @param[out] mesh 生成した円板メッシュ。
  !! @param[in] radius 円板半径 [m](省略時 0.5)。
  !! @param[in] n_theta 周方向分割数(省略時 24)。
  !! @param[in] n_r 半径方向分割数(省略時 8)。
  !! @param[in] center 円板中心座標 `(x,y,z)` [m](省略時原点)。
  subroutine make_disk(mesh, radius, n_theta, n_r, center)
    type(mesh_type), intent(out) :: mesh
    real(dp), intent(in), optional :: radius, center(3)
    integer(i32), intent(in), optional :: n_theta, n_r
    real(dp) :: r, c(3)
    integer(i32) :: nt, nr0

    r = 0.5d0; nt = 24; nr0 = 8; c = 0.0d0
    if (present(radius)) r = radius
    if (present(n_theta)) nt = n_theta
    if (present(n_r)) nr0 = n_r
    if (present(center)) c = center
    call make_annulus(mesh, radius=r, inner_radius=0.0d0, n_theta=nt, n_r=nr0, center=c)
  end subroutine make_disk

  !> XY平面上の同心リングを極座標分割し、三角形メッシュを生成する。
  !! @param[out] mesh 生成したリング(環状)メッシュ。
  !! @param[in] radius 外半径 [m](省略時 0.5)。
  !! @param[in] inner_radius 内半径 [m](省略時 0.25)。
  !! @param[in] n_theta 周方向分割数(省略時 24)。
  !! @param[in] n_r 半径方向分割数(省略時 4)。
  !! @param[in] center リング中心座標 `(x,y,z)` [m](省略時原点)。
  subroutine make_annulus(mesh, radius, inner_radius, n_theta, n_r, center)
    type(mesh_type), intent(out) :: mesh
    real(dp), intent(in), optional :: radius, inner_radius, center(3)
    integer(i32), intent(in), optional :: n_theta, n_r
    real(dp) :: r_out, r_in, c(3), pi, t0, t1, r0, r1, dr
    integer(i32) :: nt, nr0, ir, it, itri, nelem
    real(dp) :: p00(3), p01(3), p10(3), p11(3), center_p(3)
    real(dp), allocatable :: v0(:, :), v1(:, :), v2(:, :)

    r_out = 0.5d0; r_in = 0.25d0; nt = 24; nr0 = 4; c = 0.0d0
    if (present(radius)) r_out = radius
    if (present(inner_radius)) r_in = inner_radius
    if (present(n_theta)) nt = n_theta
    if (present(n_r)) nr0 = n_r
    if (present(center)) c = center
    if (nt < 3 .or. nr0 <= 0) error stop "n_theta >= 3 and n_r > 0"
    if (r_out <= 0.0d0) error stop "radius must be > 0"
    if (r_in < 0.0d0) error stop "inner_radius must be >= 0"
    if (r_in >= r_out) error stop "inner_radius must be smaller than radius"

    if (r_in <= 0.0d0) then
      nelem = nt*(2*nr0 - 1)
    else
      nelem = 2*nt*nr0
    end if
    allocate (v0(3, nelem), v1(3, nelem), v2(3, nelem))

    pi = acos(-1.0d0)
    dr = (r_out - r_in)/real(nr0, dp)
    center_p = [c(1), c(2), c(3)]
    itri = 0
    do ir = 0, nr0 - 1
      r0 = r_in + dr*real(ir, dp)
      r1 = r_in + dr*real(ir + 1, dp)
      do it = 0, nt - 1
        t0 = 2.0d0*pi*real(it, dp)/real(nt, dp)
        t1 = 2.0d0*pi*real(mod(it + 1, nt), dp)/real(nt, dp)
        p00 = [c(1) + r0*cos(t0), c(2) + r0*sin(t0), c(3)]
        p01 = [c(1) + r0*cos(t1), c(2) + r0*sin(t1), c(3)]
        p10 = [c(1) + r1*cos(t0), c(2) + r1*sin(t0), c(3)]
        p11 = [c(1) + r1*cos(t1), c(2) + r1*sin(t1), c(3)]
        if (ir == 0 .and. r_in <= 0.0d0) then
          call push_tri(v0, v1, v2, itri, center_p, p10, p11)
        else
          call push_tri(v0, v1, v2, itri, p00, p10, p11)
          call push_tri(v0, v1, v2, itri, p00, p11, p01)
        end if
      end do
    end do

    call init_mesh(mesh, v0, v1, v2)
  end subroutine make_annulus

  !> XY平面の長方形プレートから円形穴を除いたメッシュを生成する。
  !! 穴境界は `n_theta` 分割の多角形近似で表し、外周は長方形境界に一致させる。
  !! @param[out] mesh 生成した穴あきプレートメッシュ。
  !! @param[in] size_x X方向サイズ [m](省略時 1.0)。
  !! @param[in] size_y Y方向サイズ [m](省略時 1.0)。
  !! @param[in] radius 穴半径 [m](省略時 0.2)。
  !! @param[in] n_theta 穴の周方向分割数(省略時 48)。
  !! @param[in] n_r 穴縁から外周までの半径方向分割数(省略時 4)。
  !! @param[in] center プレート中心座標 `(x,y,z)` [m](省略時原点)。
  subroutine make_plate_hole(mesh, size_x, size_y, radius, n_theta, n_r, center)
    type(mesh_type), intent(out) :: mesh
    real(dp), intent(in), optional :: size_x, size_y, radius, center(3)
    integer(i32), intent(in), optional :: n_theta, n_r
    real(dp) :: sx, sy, r_hole, c(3), pi, angle, f0, f1
    real(dp) :: x_min, x_max, y_min, y_max, min_margin
    real(dp) :: p00(3), p01(3), p10(3), p11(3), corners(3, 3)
    integer(i32) :: nt, nr0, it, ir, itri, nelem, n_corner, k
    integer(i32), allocatable :: edge_ids(:)
    real(dp), allocatable :: hole_pts(:, :), outer_pts(:, :)
    real(dp), allocatable :: v0(:, :), v1(:, :), v2(:, :)

    sx = 1.0d0; sy = 1.0d0; r_hole = 0.2d0; nt = 48; nr0 = 4; c = 0.0d0
    if (present(size_x)) sx = size_x
    if (present(size_y)) sy = size_y
    if (present(radius)) r_hole = radius
    if (present(n_theta)) nt = n_theta
    if (present(n_r)) nr0 = n_r
    if (present(center)) c = center
    if (sx <= 0.0d0 .or. sy <= 0.0d0) error stop "size_x and size_y must be positive"
    if (nt < 3 .or. nr0 <= 0) error stop "n_theta >= 3 and n_r > 0"

    x_min = c(1) - 0.5d0*sx
    x_max = c(1) + 0.5d0*sx
    y_min = c(2) - 0.5d0*sy
    y_max = c(2) + 0.5d0*sy
    min_margin = min(min(c(1) - x_min, x_max - c(1)), min(c(2) - y_min, y_max - c(2)))
    if (r_hole <= 0.0d0) error stop "radius must be > 0 for plate_hole"
    if (r_hole >= min_margin) error stop "plate_hole radius must be smaller than half-width/half-height"

    allocate (hole_pts(3, nt + 1), outer_pts(3, nt + 1), edge_ids(nt + 1))
    pi = acos(-1.0d0)
    do it = 0, nt
      angle = 2.0d0*pi*real(it, dp)/real(nt, dp)
      hole_pts(:, it + 1) = [c(1) + r_hole*cos(angle), c(2) + r_hole*sin(angle), c(3)]
      call ray_to_rectangle( &
        c(1), c(2), cos(angle), sin(angle), x_min, x_max, y_min, y_max, c(3), outer_pts(:, it + 1), edge_ids(it + 1) &
        )
    end do

    nelem = 2*nt*nr0
    do it = 1, nt
      nelem = nelem + transition_corner_count(edge_ids(it), edge_ids(it + 1))
    end do
    allocate (v0(3, nelem), v1(3, nelem), v2(3, nelem))

    itri = 0
    do it = 1, nt
      do ir = 0, nr0 - 1
        f0 = real(ir, dp)/real(nr0, dp)
        f1 = real(ir + 1, dp)/real(nr0, dp)
        p00 = hole_pts(:, it) + f0*(outer_pts(:, it) - hole_pts(:, it))
        p10 = hole_pts(:, it) + f1*(outer_pts(:, it) - hole_pts(:, it))
        p01 = hole_pts(:, it + 1) + f0*(outer_pts(:, it + 1) - hole_pts(:, it + 1))
        p11 = hole_pts(:, it + 1) + f1*(outer_pts(:, it + 1) - hole_pts(:, it + 1))
        call push_tri(v0, v1, v2, itri, p00, p10, p11)
        call push_tri(v0, v1, v2, itri, p00, p11, p01)
      end do

      call transition_corners( &
        edge_ids(it), edge_ids(it + 1), x_min, x_max, y_min, y_max, c(3), corners, n_corner &
        )
      if (n_corner > 0) then
        if (n_corner == 1) then
          call push_tri(v0, v1, v2, itri, outer_pts(:, it), corners(:, 1), outer_pts(:, it + 1))
        else
          call push_tri(v0, v1, v2, itri, outer_pts(:, it), corners(:, 1), corners(:, 2))
          do k = 2, n_corner - 1
            call push_tri(v0, v1, v2, itri, outer_pts(:, it), corners(:, k), corners(:, k + 1))
          end do
          call push_tri(v0, v1, v2, itri, outer_pts(:, it), corners(:, n_corner), outer_pts(:, it + 1))
        end if
      end if
    end do

    if (itri /= nelem) error stop "plate_hole triangle bookkeeping mismatch"
    call init_mesh(mesh, v0, v1, v2)
  end subroutine make_plate_hole

  !> 直方体6面を分割数に応じて三角形化し、外向き法線向きでメッシュを生成する。
  !! @param[out] mesh 生成した直方体表面メッシュ。
  !! @param[in] size 直方体サイズ `(sx,sy,sz)` [m](省略時 `[1,1,1]`)。
  !! @param[in] center 直方体中心座標 `(x,y,z)` [m](省略時原点)。
  !! @param[in] nx X方向分割数(省略時 1)。
  !! @param[in] ny Y方向分割数(省略時 1)。
  !! @param[in] nz Z方向分割数(省略時 1)。
  subroutine make_box(mesh, size, center, nx, ny, nz)
    type(mesh_type), intent(out) :: mesh
    real(dp), intent(in), optional :: size(3), center(3)
    integer(i32), intent(in), optional :: nx, ny, nz
    real(dp) :: s(3), c(3), hx, hy, hz, dx, dy, dz, x, y, z
    integer(i32) :: nx0, ny0, nz0, nelem, itri, ix, iy, iz
    real(dp), allocatable :: v0(:, :), v1(:, :), v2(:, :)

    s = [1.0d0, 1.0d0, 1.0d0]; c = 0.0d0; nx0 = 1; ny0 = 1; nz0 = 1
    if (present(size)) s = size
    if (present(center)) c = center
    if (present(nx)) nx0 = nx
    if (present(ny)) ny0 = ny
    if (present(nz)) nz0 = nz
    if (nx0 <= 0 .or. ny0 <= 0 .or. nz0 <= 0) error stop "nx, ny, nz must be positive"

    hx = 0.5d0*s(1); hy = 0.5d0*s(2); hz = 0.5d0*s(3)
    dx = s(1)/real(nx0, dp); dy = s(2)/real(ny0, dp); dz = s(3)/real(nz0, dp)
    nelem = 4*(nx0*ny0 + ny0*nz0 + nx0*nz0)
    allocate (v0(3, nelem), v1(3, nelem), v2(3, nelem)); itri = 0

    do iz = 1, 2
      z = merge(c(3) - hz, c(3) + hz, iz == 1)
      do ix = 0, nx0 - 1
        do iy = 0, ny0 - 1
          if (iz == 1) then
            call push_tri(v0, v1, v2, itri, &
                          [c(1) - hx + dx*ix, c(2) - hy + dy*iy, z], &
                          [c(1) - hx + dx*(ix + 1), c(2) - hy + dy*(iy + 1), z], &
                          [c(1) - hx + dx*(ix + 1), c(2) - hy + dy*iy, z])
            call push_tri(v0, v1, v2, itri, &
                          [c(1) - hx + dx*ix, c(2) - hy + dy*iy, z], &
                          [c(1) - hx + dx*ix, c(2) - hy + dy*(iy + 1), z], &
                          [c(1) - hx + dx*(ix + 1), c(2) - hy + dy*(iy + 1), z])
          else
            call push_tri(v0, v1, v2, itri, &
                          [c(1) - hx + dx*ix, c(2) - hy + dy*iy, z], &
                          [c(1) - hx + dx*(ix + 1), c(2) - hy + dy*iy, z], &
                          [c(1) - hx + dx*(ix + 1), c(2) - hy + dy*(iy + 1), z])
            call push_tri(v0, v1, v2, itri, &
                          [c(1) - hx + dx*ix, c(2) - hy + dy*iy, z], &
                          [c(1) - hx + dx*(ix + 1), c(2) - hy + dy*(iy + 1), z], &
                          [c(1) - hx + dx*ix, c(2) - hy + dy*(iy + 1), z])
          end if
        end do
      end do
    end do

    do ix = 1, 2
      x = merge(c(1) - hx, c(1) + hx, ix == 1)
      do iy = 0, ny0 - 1
        do iz = 0, nz0 - 1
          if (ix == 1) then
            call push_tri(v0, v1, v2, itri, &
                          [x, c(2) - hy + dy*iy, c(3) - hz + dz*iz], &
                          [x, c(2) - hy + dy*(iy + 1), c(3) - hz + dz*(iz + 1)], &
                          [x, c(2) - hy + dy*(iy + 1), c(3) - hz + dz*iz])
            call push_tri(v0, v1, v2, itri, &
                          [x, c(2) - hy + dy*iy, c(3) - hz + dz*iz], &
                          [x, c(2) - hy + dy*iy, c(3) - hz + dz*(iz + 1)], &
                          [x, c(2) - hy + dy*(iy + 1), c(3) - hz + dz*(iz + 1)])
          else
            call push_tri(v0, v1, v2, itri, &
                          [x, c(2) - hy + dy*iy, c(3) - hz + dz*iz], &
                          [x, c(2) - hy + dy*(iy + 1), c(3) - hz + dz*iz], &
                          [x, c(2) - hy + dy*(iy + 1), c(3) - hz + dz*(iz + 1)])
            call push_tri(v0, v1, v2, itri, &
                          [x, c(2) - hy + dy*iy, c(3) - hz + dz*iz], &
                          [x, c(2) - hy + dy*(iy + 1), c(3) - hz + dz*(iz + 1)], &
                          [x, c(2) - hy + dy*iy, c(3) - hz + dz*(iz + 1)])
          end if
        end do
      end do
    end do

    do iy = 1, 2
      y = merge(c(2) - hy, c(2) + hy, iy == 1)
      do ix = 0, nx0 - 1
        do iz = 0, nz0 - 1
          if (iy == 1) then
            call push_tri(v0, v1, v2, itri, &
                          [c(1) - hx + dx*ix, y, c(3) - hz + dz*iz], &
                          [c(1) - hx + dx*(ix + 1), y, c(3) - hz + dz*iz], &
                          [c(1) - hx + dx*(ix + 1), y, c(3) - hz + dz*(iz + 1)])
            call push_tri(v0, v1, v2, itri, &
                          [c(1) - hx + dx*ix, y, c(3) - hz + dz*iz], &
                          [c(1) - hx + dx*(ix + 1), y, c(3) - hz + dz*(iz + 1)], &
                          [c(1) - hx + dx*ix, y, c(3) - hz + dz*(iz + 1)])
          else
            call push_tri(v0, v1, v2, itri, &
                          [c(1) - hx + dx*ix, y, c(3) - hz + dz*iz], &
                          [c(1) - hx + dx*(ix + 1), y, c(3) - hz + dz*(iz + 1)], &
                          [c(1) - hx + dx*(ix + 1), y, c(3) - hz + dz*iz])
            call push_tri(v0, v1, v2, itri, &
                          [c(1) - hx + dx*ix, y, c(3) - hz + dz*iz], &
                          [c(1) - hx + dx*ix, y, c(3) - hz + dz*(iz + 1)], &
                          [c(1) - hx + dx*(ix + 1), y, c(3) - hz + dz*(iz + 1)])
          end if
        end do
      end do
    end do

    call init_mesh(mesh, v0, v1, v2)
  end subroutine make_box

  !> 円柱側面を分割生成し、必要に応じて上下面キャップを追加したメッシュを生成する。
  !! @param[out] mesh 生成した円柱メッシュ。
  !! @param[in] radius 円柱半径 [m](省略時 0.5)。
  !! @param[in] height 円柱高さ [m](省略時 1.0)。
  !! @param[in] n_theta 周方向分割数(省略時 24)。
  !! @param[in] n_z 軸方向分割数(省略時 1)。
  !! @param[in] cap 上下面キャップをまとめて指定する後方互換フラグ(省略時 `.true.`)。
  !! @param[in] center 円柱中心座標 `(x,y,z)` [m](省略時原点)。
  !! @param[in] cap_top 上面キャップを生成するか(省略時 `.true.`)。
  !! @param[in] cap_bottom 下面キャップを生成するか(省略時 `.true.`)。
  subroutine make_cylinder(mesh, radius, height, n_theta, n_z, cap, center, cap_top, cap_bottom)
    type(mesh_type), intent(out) :: mesh
    real(dp), intent(in), optional :: radius, height, center(3)
    integer(i32), intent(in), optional :: n_theta, n_z
    logical, intent(in), optional :: cap, cap_top, cap_bottom
    real(dp) :: r, h, c(3), t0, t1, z0, z1, pi
    integer(i32) :: nt, nz0, iz, it, nelem, itri
    logical :: cap_top0, cap_bottom0
    real(dp), allocatable :: v0(:, :), v1(:, :), v2(:, :)

    pi = acos(-1.0d0)
    r = 0.5d0; h = 1.0d0; nt = 24; nz0 = 1; cap_top0 = .true.; cap_bottom0 = .true.; c = 0.0d0
    if (present(radius)) r = radius
    if (present(height)) h = height
    if (present(n_theta)) nt = n_theta
    if (present(n_z)) nz0 = n_z
    if (present(cap)) then
      cap_top0 = cap
      cap_bottom0 = cap
    end if
    if (present(cap_top)) cap_top0 = cap_top
    if (present(cap_bottom)) cap_bottom0 = cap_bottom
    if (present(center)) c = center
    if (nt < 3 .or. nz0 <= 0) error stop "n_theta >= 3 and n_z > 0"

    nelem = 2*nt*nz0
    if (cap_bottom0) nelem = nelem + nt
    if (cap_top0) nelem = nelem + nt
    allocate (v0(3, nelem), v1(3, nelem), v2(3, nelem)); itri = 0

    do iz = 0, nz0 - 1
      z0 = c(3) - 0.5d0*h + h*real(iz, dp)/real(nz0, dp)
      z1 = c(3) - 0.5d0*h + h*real(iz + 1, dp)/real(nz0, dp)
      do it = 0, nt - 1
        t0 = 2.0d0*pi*real(it, dp)/real(nt, dp)
        t1 = 2.0d0*pi*real(mod(it + 1, nt), dp)/real(nt, dp)
        call push_tri(v0, v1, v2, itri, &
                      [c(1) + r*cos(t0), c(2) + r*sin(t0), z0], &
                      [c(1) + r*cos(t1), c(2) + r*sin(t1), z0], &
                      [c(1) + r*cos(t1), c(2) + r*sin(t1), z1])
        call push_tri(v0, v1, v2, itri, &
                      [c(1) + r*cos(t0), c(2) + r*sin(t0), z0], &
                      [c(1) + r*cos(t1), c(2) + r*sin(t1), z1], &
                      [c(1) + r*cos(t0), c(2) + r*sin(t0), z1])
      end do
    end do

    if (cap_bottom0) then
      do it = 0, nt - 1
        t0 = 2.0d0*pi*real(it, dp)/real(nt, dp)
        t1 = 2.0d0*pi*real(mod(it + 1, nt), dp)/real(nt, dp)
        call push_tri(v0, v1, v2, itri, &
                      [c(1), c(2), c(3) - 0.5d0*h], &
                      [c(1) + r*cos(t1), c(2) + r*sin(t1), c(3) - 0.5d0*h], &
                      [c(1) + r*cos(t0), c(2) + r*sin(t0), c(3) - 0.5d0*h])
      end do
    end if
    if (cap_top0) then
      do it = 0, nt - 1
        t0 = 2.0d0*pi*real(it, dp)/real(nt, dp)
        t1 = 2.0d0*pi*real(mod(it + 1, nt), dp)/real(nt, dp)
        call push_tri(v0, v1, v2, itri, &
                      [c(1), c(2), c(3) + 0.5d0*h], &
                      [c(1) + r*cos(t0), c(2) + r*sin(t0), c(3) + 0.5d0*h], &
                      [c(1) + r*cos(t1), c(2) + r*sin(t1), c(3) + 0.5d0*h])
      end do
    end if

    call init_mesh(mesh, v0, v1, v2)
  end subroutine make_cylinder

  !> 経度・緯度分割に基づき球面三角形メッシュを生成する。
  !! @param[out] mesh 生成した球面三角形メッシュ。
  !! @param[in] radius 球半径 [m](省略時 0.5)。
  !! @param[in] n_lon 経度方向分割数(省略時 24)。
  !! @param[in] n_lat 緯度方向分割数(省略時 12)。
  !! @param[in] center 球中心座標 `(x,y,z)` [m](省略時原点)。
  subroutine make_sphere(mesh, radius, n_lon, n_lat, center)
    type(mesh_type), intent(out) :: mesh
    real(dp), intent(in), optional :: radius, center(3)
    integer(i32), intent(in), optional :: n_lon, n_lat
    real(dp) :: r, c(3), t0, t1, p0, p1, pi
    integer(i32) :: nl, nphi, ilon, ilat, nelem, itri
    real(dp) :: a(3), b(3), c0(3), d(3)
    real(dp), allocatable :: v0(:, :), v1(:, :), v2(:, :)

    pi = acos(-1.0d0)
    r = 0.5d0; nl = 24; nphi = 12; c = 0.0d0
    if (present(radius)) r = radius
    if (present(n_lon)) nl = n_lon
    if (present(n_lat)) nphi = n_lat
    if (present(center)) c = center
    if (nl < 3 .or. nphi < 2) error stop "n_lon >= 3 and n_lat >= 2"

    nelem = 2*nl*(nphi - 1)
    allocate (v0(3, nelem), v1(3, nelem), v2(3, nelem)); itri = 0

    do ilat = 0, nphi - 1
      p0 = pi*real(ilat, dp)/real(nphi, dp)
      p1 = pi*real(ilat + 1, dp)/real(nphi, dp)
      do ilon = 0, nl - 1
        t0 = 2.0d0*pi*real(ilon, dp)/real(nl, dp)
        t1 = 2.0d0*pi*real(mod(ilon + 1, nl), dp)/real(nl, dp)
        call sph(r, c, t0, p0, a)
        call sph(r, c, t1, p0, b)
        call sph(r, c, t0, p1, c0)
        call sph(r, c, t1, p1, d)
        if (ilat == 0) then
          call push_tri(v0, v1, v2, itri, a, c0, d)
        else if (ilat == nphi - 1) then
          call push_tri(v0, v1, v2, itri, a, d, b)
        else
          call push_tri(v0, v1, v2, itri, a, d, b)
          call push_tri(v0, v1, v2, itri, a, c0, d)
        end if
      end do
    end do

    call init_mesh(mesh, v0, v1, v2)
  end subroutine make_sphere

  !> XY平面上で、中心からのレイと長方形境界の最短交点を返す。
  subroutine ray_to_rectangle(hx, hy, dx, dy, x_min, x_max, y_min, y_max, z_plane, p, edge_id)
    real(dp), intent(in) :: hx, hy, dx, dy, x_min, x_max, y_min, y_max, z_plane
    real(dp), intent(out) :: p(3)
    integer(i32), intent(out) :: edge_id
    real(dp) :: s_best, s, x, y
    real(dp), parameter :: eps = 1.0d-12

    s_best = huge(1.0d0)
    edge_id = 0_i32
    p = 0.0d0

    if (dx > eps) then
      s = (x_max - hx)/dx
      y = hy + s*dy
      if (s > eps .and. y >= y_min - eps .and. y <= y_max + eps .and. s < s_best) then
        s_best = s
        edge_id = 2_i32
        p = [x_max, y, z_plane]
      end if
    else if (dx < -eps) then
      s = (x_min - hx)/dx
      y = hy + s*dy
      if (s > eps .and. y >= y_min - eps .and. y <= y_max + eps .and. s < s_best) then
        s_best = s
        edge_id = 1_i32
        p = [x_min, y, z_plane]
      end if
    end if
    if (dy > eps) then
      s = (y_max - hy)/dy
      x = hx + s*dx
      if (s > eps .and. x >= x_min - eps .and. x <= x_max + eps .and. s < s_best) then
        s_best = s
        edge_id = 4_i32
        p = [x, y_max, z_plane]
      end if
    else if (dy < -eps) then
      s = (y_min - hy)/dy
      x = hx + s*dx
      if (s > eps .and. x >= x_min - eps .and. x <= x_max + eps .and. s < s_best) then
        edge_id = 3_i32
        p = [x, y_min, z_plane]
      end if
    end if

    if (edge_id == 0_i32) error stop "failed to intersect ray with rectangle boundary"
  end subroutine ray_to_rectangle

  !> 長方形外周を反時計回りに見たとき、辺遷移で通過するコーナー数を返す。
  integer(i32) function transition_corner_count(edge_from, edge_to) result(n_corner)
    integer(i32), intent(in) :: edge_from, edge_to
    integer(i32) :: i_from, i_to

    i_from = edge_order_index(edge_from)
    i_to = edge_order_index(edge_to)
    n_corner = modulo(i_to - i_from, 4_i32)
  end function transition_corner_count

  !> 辺遷移時に通過するコーナー列(最大3点)を返す。
  subroutine transition_corners(edge_from, edge_to, x_min, x_max, y_min, y_max, z_plane, corners, n_corner)
    integer(i32), intent(in) :: edge_from, edge_to
    real(dp), intent(in) :: x_min, x_max, y_min, y_max, z_plane
    real(dp), intent(out) :: corners(3, 3)
    integer(i32), intent(out) :: n_corner
    integer(i32) :: k, edge_curr, edge_next

    corners = 0.0d0
    n_corner = transition_corner_count(edge_from, edge_to)
    edge_curr = edge_from
    do k = 1, n_corner
      edge_next = edge_next_ccw(edge_curr)
      call corner_between(edge_curr, edge_next, x_min, x_max, y_min, y_max, z_plane, corners(:, k))
      edge_curr = edge_next
    end do
  end subroutine transition_corners

  !> 境界辺の反時計回り順序インデックスを返す。
  integer(i32) function edge_order_index(edge_id) result(idx)
    integer(i32), intent(in) :: edge_id

    select case (edge_id)
    case (2_i32)
      idx = 0_i32
    case (4_i32)
      idx = 1_i32
    case (1_i32)
      idx = 2_i32
    case (3_i32)
      idx = 3_i32
    case default
      error stop "unknown edge id"
    end select
  end function edge_order_index

  !> 境界辺の次(反時計回り)を返す。
  integer(i32) function edge_next_ccw(edge_id) result(next_id)
    integer(i32), intent(in) :: edge_id

    select case (edge_id)
    case (2_i32)
      next_id = 4_i32
    case (4_i32)
      next_id = 1_i32
    case (1_i32)
      next_id = 3_i32
    case (3_i32)
      next_id = 2_i32
    case default
      error stop "unknown edge id"
    end select
  end function edge_next_ccw

  !> 反時計回りに隣接する辺ペアに対し、対応する長方形コーナー座標を返す。
  subroutine corner_between(edge_from, edge_to, x_min, x_max, y_min, y_max, z_plane, corner)
    integer(i32), intent(in) :: edge_from, edge_to
    real(dp), intent(in) :: x_min, x_max, y_min, y_max, z_plane
    real(dp), intent(out) :: corner(3)

    select case (edge_from)
    case (2_i32)
      if (edge_to /= 4_i32) error stop "invalid edge transition"
      corner = [x_max, y_max, z_plane]
    case (4_i32)
      if (edge_to /= 1_i32) error stop "invalid edge transition"
      corner = [x_min, y_max, z_plane]
    case (1_i32)
      if (edge_to /= 3_i32) error stop "invalid edge transition"
      corner = [x_min, y_min, z_plane]
    case (3_i32)
      if (edge_to /= 2_i32) error stop "invalid edge transition"
      corner = [x_max, y_min, z_plane]
    case default
      error stop "unknown edge id"
    end select
  end subroutine corner_between

  !> 球座標 `(theta, phi)` を中心 `c`・半径 `r` の直交座標へ変換する。
  !! @param[in] r 球半径 [m]。
  !! @param[in] c 球中心座標 `(x,y,z)` [m]。
  !! @param[in] theta 方位角 [rad]。
  !! @param[in] phi 極角(+Z軸基準) [rad]。
  !! @param[out] p 変換後の直交座標 `(x,y,z)` [m]。
  pure subroutine sph(r, c, theta, phi, p)
    real(dp), intent(in) :: r, c(3), theta, phi
    real(dp), intent(out) :: p(3)
    p(1) = c(1) + r*sin(phi)*cos(theta)
    p(2) = c(2) + r*sin(phi)*sin(theta)
    p(3) = c(3) + r*cos(phi)
  end subroutine sph

  !> 三角形頂点 `a,b,c` を出力配列の次インデックスへ書き込む。
  !! @param[inout] v0 三角形頂点0を保持する配列 `v0(3,nelem)`。
  !! @param[inout] v1 三角形頂点1を保持する配列 `v1(3,nelem)`。
  !! @param[inout] v2 三角形頂点2を保持する配列 `v2(3,nelem)`。
  !! @param[inout] itri 現在までに書き込んだ三角形数(呼び出し内で1増加)。
  !! @param[in] a 追加する三角形の頂点A座標。
  !! @param[in] b 追加する三角形の頂点B座標。
  !! @param[in] c 追加する三角形の頂点C座標。
  pure subroutine push_tri(v0, v1, v2, itri, a, b, c)
    real(dp), intent(inout) :: v0(:, :), v1(:, :), v2(:, :)
    integer(i32), intent(inout) :: itri
    real(dp), intent(in) :: a(3), b(3), c(3)
    itri = itri + 1
    v0(:, itri) = a
    v1(:, itri) = b
    v2(:, itri) = c
  end subroutine push_tri

end module bem_templates