bem_importers.f90 Source File


This file depends on

sourcefile~~bem_importers.f90~~EfferentGraph sourcefile~bem_importers.f90 bem_importers.f90 sourcefile~bem_kinds.f90 bem_kinds.f90 sourcefile~bem_importers.f90->sourcefile~bem_kinds.f90 sourcefile~bem_mesh.f90 bem_mesh.f90 sourcefile~bem_importers.f90->sourcefile~bem_mesh.f90 sourcefile~bem_types.f90 bem_types.f90 sourcefile~bem_importers.f90->sourcefile~bem_types.f90 sourcefile~bem_mesh.f90->sourcefile~bem_kinds.f90 sourcefile~bem_mesh.f90->sourcefile~bem_types.f90 sourcefile~bem_panel_geometry.f90 bem_panel_geometry.f90 sourcefile~bem_mesh.f90->sourcefile~bem_panel_geometry.f90 sourcefile~bem_periodic2_axes.f90 bem_periodic2_axes.f90 sourcefile~bem_mesh.f90->sourcefile~bem_periodic2_axes.f90 sourcefile~bem_triangle_quadrature.f90 bem_triangle_quadrature.f90 sourcefile~bem_mesh.f90->sourcefile~bem_triangle_quadrature.f90 sourcefile~bem_types.f90->sourcefile~bem_kinds.f90 sourcefile~bem_outflow_spectrum.f90 bem_outflow_spectrum.f90 sourcefile~bem_types.f90->sourcefile~bem_outflow_spectrum.f90 sourcefile~bem_outflow_spectrum.f90->sourcefile~bem_kinds.f90 sourcefile~bem_panel_geometry.f90->sourcefile~bem_kinds.f90 sourcefile~bem_periodic2_axes.f90->sourcefile~bem_kinds.f90 sourcefile~bem_periodic2_axes.f90->sourcefile~bem_types.f90 sourcefile~bem_string_utils.f90 bem_string_utils.f90 sourcefile~bem_periodic2_axes.f90->sourcefile~bem_string_utils.f90 sourcefile~bem_triangle_quadrature.f90->sourcefile~bem_kinds.f90 sourcefile~bem_triangle_quadrature.f90->sourcefile~bem_panel_geometry.f90 sourcefile~bem_constants.f90 bem_constants.f90 sourcefile~bem_triangle_quadrature.f90->sourcefile~bem_constants.f90 sourcefile~bem_constants.f90->sourcefile~bem_kinds.f90

Files dependent on this one

sourcefile~~bem_importers.f90~~AfferentGraph sourcefile~bem_importers.f90 bem_importers.f90 sourcefile~bem_app_config_mesh_runtime.f90 bem_app_config_mesh_runtime.f90 sourcefile~bem_app_config_mesh_runtime.f90->sourcefile~bem_importers.f90 sourcefile~bem_app_config_runtime.f90 bem_app_config_runtime.f90 sourcefile~bem_app_config_runtime.f90->sourcefile~bem_app_config_mesh_runtime.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_runtime.f90 sourcefile~bem_simulator.f90->sourcefile~bem_app_config.f90 sourcefile~bem_simulator_io.f90 bem_simulator_io.f90 sourcefile~bem_simulator_io.f90->sourcefile~bem_app_config_runtime.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_app_config_runtime.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_simulator.f90 sourcefile~bem_zhao_outflow_refresh.f90 bem_zhao_outflow_refresh.f90 sourcefile~bem_simulator_loop.f90->sourcefile~bem_zhao_outflow_refresh.f90 sourcefile~bem_simulator_charge.f90 bem_simulator_charge.f90 sourcefile~bem_simulator_charge.f90->sourcefile~bem_simulator.f90 sourcefile~bem_simulator_particles.f90 bem_simulator_particles.f90 sourcefile~bem_simulator_particles.f90->sourcefile~bem_simulator.f90 sourcefile~bem_simulator_stats.f90 bem_simulator_stats.f90 sourcefile~bem_simulator_stats.f90->sourcefile~bem_simulator.f90 sourcefile~bem_zhao_outflow_refresh.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

Source Code

!> OBJメッシュを走査・解析し、内部 `mesh_type` へ変換するインポートモジュール。
module bem_importers
  use bem_kinds, only: dp, i32
  use bem_types, only: mesh_type
  use bem_mesh, only: init_mesh
  use, intrinsic :: iso_fortran_env, only: iostat_end, iostat_eor
  implicit none
contains

  !> OBJファイルを2パス(件数取得→実データ読込)で読み込み、メッシュを構築する。
  !! @param[in] path 読み込むOBJファイルのパス。
  !! @param[out] mesh OBJから構築した三角形メッシュ。
  subroutine load_obj_mesh(path, mesh)
    character(len=*), intent(in) :: path
    type(mesh_type), intent(out) :: mesh
    integer(i32) :: nvert, ntri
    real(dp), allocatable :: vertices(:, :)
    integer(i32), allocatable :: faces(:, :)

    call scan_obj(path, nvert, ntri)
    if (nvert == 0 .or. ntri == 0) error stop "OBJ has no vertices/faces"
    allocate (vertices(3, nvert), faces(3, ntri))
    call parse_obj(path, vertices, faces)
    call build_mesh_from_indexed(vertices, faces, mesh)
  end subroutine load_obj_mesh

  !> 頂点配列と三角形インデックス配列から面頂点配列(v0,v1,v2)を展開して初期化する。
  !! @param[in] vertices 頂点座標配列 `vertices(3,nvert)`。
  !! @param[in] faces 三角形の頂点インデックス配列 `faces(3,ntri)`(1始まり)。
  !! @param[out] mesh 展開後に初期化した三角形メッシュ。
  subroutine build_mesh_from_indexed(vertices, faces, mesh)
    real(dp), intent(in) :: vertices(:, :)
    integer(i32), intent(in) :: faces(:, :)
    type(mesh_type), intent(out) :: mesh
    real(dp), allocatable :: v0(:, :), v1(:, :), v2(:, :)
    integer(i32) :: i, ntri

    if (size(vertices, 1) /= 3 .or. size(faces, 1) /= 3) then
      error stop "vertices/faces shape mismatch"
    end if

    ntri = size(faces, 2)
    allocate (v0(3, ntri), v1(3, ntri), v2(3, ntri))
    do i = 1, ntri
      v0(:, i) = vertices(:, faces(1, i))
      v1(:, i) = vertices(:, faces(2, i))
      v2(:, i) = vertices(:, faces(3, i))
    end do
    call init_mesh(mesh, v0, v1, v2)
  end subroutine build_mesh_from_indexed

  !> OBJを行走査して頂点数と三角形分割後の面数を事前計数する。
  !! @param[in] path 走査対象のOBJファイルパス。
  !! @param[out] nvert 頂点行 `v` の総数。
  !! @param[out] ntri 面行を扇形分割した後の三角形総数。
  subroutine scan_obj(path, nvert, ntri)
    character(len=*), intent(in) :: path
    integer(i32), intent(out) :: nvert, ntri
    character(len=:), allocatable :: line
    integer :: u, ios, ntok

    nvert = 0
    ntri = 0
    open (newunit=u, file=path, status='old', action='read', iostat=ios)
    if (ios /= 0) error stop "failed to open OBJ"

    do
      call read_obj_line(u, line, ios)
      if (ios /= 0) exit
      if (is_vertex_line(line)) nvert = nvert + 1
      if (is_face_line(line)) then
        ntok = count_face_tokens(line)
        if (ntok >= 3) ntri = ntri + (ntok - 2)
      end if
    end do
    close (u)
  end subroutine scan_obj

  !> OBJの頂点/面行を解析し、負インデックス対応で配列へ格納する。
  !! @param[in] path 読み込み対象のOBJファイルパス。
  !! @param[out] vertices 解析した頂点座標配列 `vertices(3,nvert)`。
  !! @param[out] faces 扇形分割後の三角形インデックス配列 `faces(3,ntri)`。
  subroutine parse_obj(path, vertices, faces)
    character(len=*), intent(in) :: path
    real(dp), intent(out) :: vertices(:, :)
    integer(i32), intent(out) :: faces(:, :)
    character(len=:), allocatable :: line
    integer :: u, ios, i
    integer(i32) :: iv, itri
    integer(i32), allocatable :: idx(:)

    iv = 0
    itri = 0
    open (newunit=u, file=path, status='old', action='read', iostat=ios)
    if (ios /= 0) error stop "failed to open OBJ"

    do
      call read_obj_line(u, line, ios)
      if (ios /= 0) exit
      if (is_vertex_line(line)) then
        iv = iv + 1
        call parse_vertex_line(line, vertices(:, iv))
      else if (is_face_line(line)) then
        call parse_face_line(line, iv, idx)
        do i = 2, size(idx) - 1
          itri = itri + 1
          faces(:, itri) = [idx(1), idx(i), idx(i + 1)]
        end do
      end if
    end do
    close (u)
  end subroutine parse_obj

  !> OBJの1行を長さ制限なしで読み、行末の CR を除く。
  !! 固定長バッファで読むと長い面行が短い有効な面へ切り詰められるため、
  !! 非前進読込で行末まで連結する。
  !! @param[in] u 読み込み中の装置番号。
  !! @param[out] line 読み込んだ1行(行末の改行と CR を除く)。
  !! @param[out] ios 0 なら1行を読んだ。ファイル終端または読込失敗では非0。
  subroutine read_obj_line(u, line, ios)
    integer, intent(in) :: u
    character(len=:), allocatable, intent(out) :: line
    integer, intent(out) :: ios
    character(len=1024) :: chunk
    integer :: nread

    line = ''
    do
      read (u, '(a)', advance='no', size=nread, iostat=ios) chunk
      line = line//chunk(1:nread)
      if (ios == iostat_eor) then
        ios = 0
        exit
      end if
      if (ios /= 0) then
        ! 改行のない最終行は、その行を返してから次の呼出しで終端を返す。
        if (ios == iostat_end .and. len(line) > 0) ios = 0
        exit
      end if
    end do
    call strip_cr(line)
  end subroutine read_obj_line

  !> 与えられた行がOBJ頂点行(`v `)かを判定する。
  !! @param[in] line 判定対象の1行文字列。
  !! @return is_vertex_line 関数の戻り値。
  logical pure function is_vertex_line(line)
    character(len=*), intent(in) :: line
    character(:), allocatable :: adj

    adj = adjustl(line)
    is_vertex_line = (len_trim(adj) > 1) .and. (adj(1:2) == 'v ')
  end function is_vertex_line

  !> 与えられた行がOBJ面行(`f `)かを判定する。
  !! @param[in] line 判定対象の1行文字列。
  !! @return is_face_line 関数の戻り値。
  logical pure function is_face_line(line)
    character(len=*), intent(in) :: line
    character(:), allocatable :: adj

    adj = adjustl(line)
    is_face_line = len_trim(adj) > 1 .and. adj(1:2) == 'f '
  end function is_face_line

  !> 面行に含まれる頂点トークン数を数え、扇形分割時の三角形数算出に使う。
  !! @param[in] line `f` で始まるOBJ面行。
  !! @return count_face_tokens 関数の戻り値。
  integer(i32) pure function count_face_tokens(line)
    character(len=*), intent(in) :: line
    character(len=:), allocatable :: s
    integer :: pos, first, last

    s = trim(adjustl(line))
    pos = 3
    count_face_tokens = 0
    do
      call next_face_token(s, pos, first, last)
      if (first > last) exit
      count_face_tokens = count_face_tokens + 1
    end do
  end function count_face_tokens

  !> 面行の `pos` 以降にある次の空白区切りトークンの範囲を返す。
  !! 計数パスと解析パスが同じ規則でトークンを数えるための共通 tokenizer。
  !! @param[in] s `f` で始まる左詰めの面行。
  !! @param[inout] pos 走査開始位置。戻り値はトークン直後の位置。
  !! @param[out] first トークン先頭位置。トークンがなければ `first > last`。
  !! @param[out] last トークン末尾位置。
  pure subroutine next_face_token(s, pos, first, last)
    character(len=*), intent(in) :: s
    integer, intent(inout) :: pos
    integer, intent(out) :: first, last
    integer :: n

    n = len_trim(s)
    do while (pos <= n)
      if (s(pos:pos) /= ' ') exit
      pos = pos + 1
    end do
    first = pos
    last = pos - 1
    do while (pos <= n)
      if (s(pos:pos) == ' ') exit
      last = pos
      pos = pos + 1
    end do
  end subroutine next_face_token

  !> `v x y z` 形式の頂点行を3次元座標へ変換する。
  !! @param[in] line OBJ頂点行(例: `v 0.0 1.0 2.0`)。
  !! @param[out] p 解析した頂点座標 `(x,y,z)`。
  subroutine parse_vertex_line(line, p)
    character(len=*), intent(in) :: line
    real(dp), intent(out) :: p(3)
    character(len=:), allocatable :: s
    s = trim(adjustl(line))
    read (s(3:), *) p(1), p(2), p(3)
  end subroutine parse_vertex_line

  !> `f` 行の頂点参照を抽出し、`v/vt/vn` 形式から頂点インデックスのみを取り出す。
  !! @param[in] line OBJ面行(`f i j k ...`)。
  !! @param[in] nvert 負インデックス解決に使う現在有効な頂点数。
  !! @param[out] idx 抽出した頂点インデックス配列(トークン数と同じ長さ)。
  subroutine parse_face_line(line, nvert, idx)
    character(len=*), intent(in) :: line
    integer(i32), intent(in) :: nvert
    integer(i32), allocatable, intent(out) :: idx(:)
    character(len=:), allocatable :: s
    integer :: pos, first, last, slash, k
    integer(i32) :: vi

    s = trim(adjustl(line))
    allocate (idx(count_face_tokens(s)))
    pos = 3
    do k = 1, size(idx)
      call next_face_token(s, pos, first, last)
      slash = index(s(first:last), '/')
      if (slash > 0) last = first + slash - 2
      read (s(first:last), *) vi
      if (vi < 0) vi = nvert + vi + 1
      if (vi <= 0 .or. vi > nvert) error stop "OBJ face index out of range"
      idx(k) = vi
    end do
  end subroutine parse_face_line

  !> 行末の CR 文字 (char 13) をスペースに置換し、CRLF 改行の OBJ に対応する。
  !! @param[inout] line 処理対象の行文字列。
  subroutine strip_cr(line)
    character(len=*), intent(inout) :: line
    integer :: i
    do i = len_trim(line), 1, -1
      if (ichar(line(i:i)) == 13) then
        line(i:i) = ' '
      else
        exit
      end if
    end do
  end subroutine strip_cr

end module bem_importers