raylib

The open source C library raylib provides an API for 2-D and 3-D graphics programming, mainly for computer and video games, on all major platforms without external dependencies. Interface bindings are available for more than 60 programming languages, including Fortran. The library features:

Raylib can be combined with extra libraries for additional functionality, like raygui, a simple immediate-mode GUI library.

Voyager model in Fortran
Fig. 1: NASA model of the Voyager spacecraft rendered with Fortran and raylib

Installation

The library has to be compiled from source if no binary package is available for the targeted operating system. On FreeBSD, unpack the source archive (31.2 MiB), create a new Makefile with CMake, then build, and install raylib system-wide:

$ tar xfvz raylib-5.0.tar.gz
$ cd raylib-5.0/
$ mkdir build && cd build/
$ cmake ..
$ make PLATFORM=PLATFORM_DESKTOP
$ make install

Alternatively, you may want to use the current master branch of the source code repository instead. Afterwards, download or clone the Fortran 2018 interface bindings fortran-raylib, and execute the Makefile:

$ git clone https://github.com/interkosmos/fortran-raylib
$ cd fortran-raylib/
$ make

Alternatively, fortran-raylib may be added as a dependency to the build manifest of the Fortran Package Manager, if preferred:

[dependencies]
fortran-raylib = { git = "https://github.com/interkosmos/fortran-raylib.git" }

Either link against the static raylib library libraylib.a, or the shared one -lraylib, additionally to the interface bindings libfortran-raylib.a. See the bindings documentation for more information.

Example

The example program shown in this section will render a 3-D model of the Voyager that was created by NASA/JPL-Caltech and released as public domain. The model consists of 17,602 polygons, and is separated into three files:

The program rotates the model each frame in Z (fig. 1). Look around the 3-D world by using the mouse, move by pressing the keys W, A, S, D, and rotate with Q and E.

! voyager.f90
program main
    use, intrinsic :: iso_c_binding
    use :: raylib
    implicit none (type, external)

    integer, parameter :: SCREEN_WIDTH  = 800
    integer, parameter :: SCREEN_HEIGHT = 450

    real                 :: angle
    type(camera3d_type)  :: camera
    type(model_type)     :: model
    type(texture2d_type) :: texture
    type(vector3_type)   :: position, rotation, scale

    ! Enable V-Sync and anti-aliasing, initialise window, and disable mouse pointer.
    call set_config_flags(ior(FLAG_VSYNC_HINT, FLAG_MSAA_4X_HINT))
    call init_window(SCREEN_WIDTH, SCREEN_HEIGHT, 'Voyager' // c_null_char)
    call disable_cursor()

    ! Define camera to look into our 3-D world.
    camera%position   = vector3_type(10.0, 10.0, 10.0) ! Camera position vector.
    camera%target     = vector3_type(0.0, 0.0, 0.0)    ! Camera target vector.
    camera%up         = vector3_type(0.0, 1.0, 0.0)    ! Camera up vector.
    camera%fov_y      = 45.0                           ! Camera field of view [deg].
    camera%projection = CAMERA_PERSPECTIVE             ! Camera projection.

    ! Load voyager.obj, voyager.mtl, and voyager.png.
    model   = load_model('voyager.obj' // c_null_char)
    texture = load_texture('voyager.png' // c_null_char)

    ! Set texture.
    call set_model_diffuse(model, texture)

    ! Set model parameters.
    angle    = 0.0                         ! Rotation angle [deg].
    rotation = vector3_type(0.0, 0.0, 1.0) ! Rotation vector.
    scale    = vector3_type(2.0, 2.0, 2.0) ! Scale factor.

    ! The rendering loop.
    do while (.not. window_should_close())
        call update_camera(camera, CAMERA_FIRST_PERSON)

        angle = modulo(angle + 0.1, 360.0)

        call begin_drawing()
            call clear_background(RAYWHITE)

            call begin_mode3d(camera)
                call draw_model_ex(model, position, rotation, angle, scale, WHITE)
            call end_mode3d()

            call draw_fps(10, 10)
        call end_drawing()
    end do

    ! Clean-up and close window.
    call unload_texture(texture)
    call unload_model(model)
    call close_window()
contains
    subroutine set_model_diffuse(model, texture)
        !! Utility routine to set the material (texture) of a model.
        type(model_type),     intent(inout) :: model   !! Model to modify.
        type(texture2d_type), intent(inout) :: texture !! Texture to add to model.

        type(material_type),     pointer :: material_ptrs(:)
        type(material_map_type), pointer :: material_map_ptrs(:)

        ! We have to add 1 to the array indices as a work-around, as we can't set
        ! the lower bounds of the pointer arrays with `c_f_pointer()` yet (new
        ! Fortran 2023 feature).
        call c_f_pointer(model%materials, material_ptrs, [ model%material_count ])
        call c_f_pointer(material_ptrs(1)%maps, material_map_ptrs, [ MATERIAL_MAP_BRDF + 1 ])
        material_map_ptrs(MATERIAL_MAP_DIFFUSE + 1)%texture = texture
    end subroutine set_model_diffuse
end program main

All character strings passed to raylib have to be properly null-terminated with c_null_char at the end. Otherwise, weird segmentation faults may occur.

On FreeBSD, build and link the executable with:

$ gfortran13 -o voyager voyager.f90 libfortran-raylib.a libraylib.a -lGL -lpthread -lm

To link against the shared raylib library, replace libraylib.a with -lraylib. Make sure that voyager.obj, voyager.mtl, and voyager.png are in the same directory as the binary. Then, run:

$ ./voyager

Fortran Libraries

References