!> OBJメッシュを走査・解析し、内部 `mesh_type` へ変換するインポートモジュール。 module bem_importers use bem_kinds, only: dp, i32 use bem_types, only: mesh_type use bem_mesh, only: init_mesh 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=1024) :: 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 read (u, '(A)', iostat=ios) line if (ios /= 0) exit call strip_cr(line) 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=1024) :: line integer :: u, ios, i integer(i32) :: iv, itri, ntok, idx(512) 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 read (u, '(A)', iostat=ios) line if (ios /= 0) exit call strip_cr(line) 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, ntok) if (ntok >= 3) then do i = 2, ntok - 1 itri = itri + 1 faces(:, itri) = [idx(1), idx(i), idx(i + 1)] end do end if end if end do close (u) end subroutine parse_obj !> 与えられた行が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=1024) :: s integer :: pos, n, i s = trim(adjustl(line)) pos = 3 n = len_trim(s) count_face_tokens = 0 do while (pos <= n) do while (pos <= n .and. s(pos:pos) == ' ') pos = pos + 1 end do if (pos > n) exit count_face_tokens = count_face_tokens + 1 do i = pos, n if (s(i:i) == ' ') then pos = i + 1 exit end if if (i == n) pos = n + 1 end do end do end function count_face_tokens !> `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=1024) :: 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 抽出した頂点インデックス配列(先頭 `ntok` 個が有効)。 !! @param[out] ntok 抽出した頂点参照トークン数。 subroutine parse_face_line(line, nvert, idx, ntok) character(len=*), intent(in) :: line integer(i32), intent(in) :: nvert integer(i32), intent(out) :: idx(:) integer(i32), intent(out) :: ntok character(len=1024) :: s, tok integer :: pos, n, slash, i integer(i32) :: vi s = trim(adjustl(line)) pos = 3 n = len_trim(s) ntok = 0 do while (pos <= n) do while (pos <= n .and. s(pos:pos) == ' ') pos = pos + 1 end do if (pos > n) exit tok = '' do i = pos, n if (s(i:i) == ' ') then tok = s(pos:i - 1) pos = i + 1 exit end if if (i == n) then tok = s(pos:n) pos = n + 1 end if end do slash = index(tok, '/') if (slash > 0) tok = tok(1:slash - 1) read (tok, *) vi if (vi < 0) vi = nvert + vi + 1 if (vi <= 0 .or. vi > nvert) error stop "OBJ face index out of range" ntok = ntok + 1 idx(ntok) = 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