Search code examples
iofortranspecial-charactersgfortrancad

How to use Fortran to read the face information from a *.obj file


Problem

How can modern Fortran navigate a file with double forward slashes between entries, as in the *.obj format? The goal is to extract the vertex index (first entry) and ignore the vertex normal index (second entry).

Example

For example, for this snippet,

f 297//763 298//763 296//763
f 296//764 298//764 295//764
f 384//765 385//765 382//765
f 384//766 382//766 383//766

the goal is to create an array like this:

face ( 1 ) = [297, 298, 296]
face ( 2 ) = [296, 298, 295]
face ( 3 ) = [384, 385, 382]
face ( 4 ) = [384, 382, 383]

Extra points for an answer which would adopt to a richer format like

f a//b//c  d//e//f g//h//i j//k//l

Other posts

The answer for [How to get Wavefront .obj file with 3 faces (traingle) points to a deleted blog. This post [How to read numeric data from a string in FORTRAN was not relevant.

References

Three references on the *.obj format: Object Files (.obj), B1. Object Files (.obj), Wavefront .obj file


Solution

  • As suggested in the comments, we can replace / by a space using sed etc. We can also scan each character one by one in Fortran (see below). We then read in all integers into vals and select the desired part as vals( 1:6:2 ). A similar approach can be used for f a//b//c d//e//f ... etc by changing 1:6:2 to 1:12:3 etc.

    [test.f90]
    program main
        implicit none
        character(100) buf
        integer vals(10), ios, i
    
        open(10, file="test.dat", status="old")
        do
            read(10, "(a)", iostat=ios) buf
            if (ios /= 0) exit
    
            do i = 1, len(buf)
                if (buf(i:i) == "/") buf(i:i) = " "   !! replace "/" by " "
            enddo
    
            read(buf(2:), *) vals( 1:6 )  !! read all items
            print *, vals( 1:6:2 )  !! select items 1, 3, 5
        enddo
        close(10)
    end
    
    [test.dat]
    f 297//763 298//763 296//763
    f 296//764 298//764 295//764
    f 384//765 385//765 382//765
    f 384//766 382//766 383//766
    
    $ gfortran test.f90 && ./a.out
         297         298         296
         296         298         295
         384         385         382
         384         382         383
    

    Just for fun, here is a similar code in Python, which is shorter thanks to replace() and split(). If we have some similar routines, I guess the above code may also become shorter.

    dat = []
    for line in open("test.dat").readlines():
        dat.append( line[1:] .replace("/", " ") .split() [::2] )
    
    import numpy as np
    face = np.array(dat, dtype=int)
    print(face)
    
    $ python test.py
    [[297 298 296]
     [296 298 295]
     [384 385 382]
     [384 382 383]]