3d_animation_demo.f90 Source File


Source Code

program three_d_animation_demo
    !! Animate a 3D Lissajous curve by rotating the camera and save it to
    !! MP4 and ASCII.
    !!
    !! The data and the axis limits stay fixed; only the view azimuth advances
    !! per frame (matplotlib's ax.view_init(elev, azim) pattern), so the 3D box
    !! keeps a constant shape instead of rescaling.
    use iso_fortran_env, only: wp => real64
    use fortplot
    use fortplot_animation
    use fortplot_system_runtime, only: create_directory_runtime
    implicit none

    integer, parameter :: N_POINTS = 200
    integer, parameter :: N_FRAMES = 60
    character(len=*), parameter :: OUTDIR = "output/example/fortran/3d_animation_demo"

    type(figure_t), pointer :: pfig
    type(animation_t) :: anim
    real(wp), dimension(N_POINTS) :: xs, ys, zs
    real(wp) :: t
    integer :: i, status
    logical :: ok

    call create_directory_runtime(OUTDIR, ok)
    if (.not. ok) print *, "WARNING: could not create ", OUTDIR

    do i = 1, N_POINTS
        t = real(i - 1, wp) * 2.0_wp * 3.141592653589793_wp / real(N_POINTS - 1, wp)
        xs(i) = sin(3.0_wp * t)
        ys(i) = cos(2.0_wp * t)
        zs(i) = sin(t) * cos(t)
    end do

    call figure(figsize=[8.0_wp, 6.0_wp])
    pfig => get_global_figure()
    call add_3d_plot(xs, ys, zs, label="Lissajous 3D", linestyle='-')
    call xlabel("X")
    call ylabel("Y")
    call title("Rotating 3D Lissajous")
    call set_fixed_limits()

    anim = FuncAnimation(rotate_view, frames=N_FRAMES, interval=33, fig=pfig)

    print *, "Saving 3D animation as MP4..."
    call save_animation(anim, OUTDIR // "/animation.mp4", fps=30, status=status)
    call report_status("MP4", status)

    print *, "Saving 3D animation as ASCII..."
    call save_animation(anim, OUTDIR // "/animation.txt", status=status)
    call report_status("ASCII", status)

    if (status == 0) then
        print *, "Replay with: fpm run --target fortplot_play_ascii -- ", &
            OUTDIR // "/animation.txt --fps 24 --loop"
    end if

contains

    subroutine set_fixed_limits()
        !! Pin the axis box so autoscale does not refit between frames.
        call xlim(-1.2_wp, 1.2_wp)
        call ylim(-1.2_wp, 1.2_wp)
    end subroutine set_fixed_limits

    subroutine rotate_view(frame)
        integer, intent(in) :: frame
        real(wp) :: azim

        azim = -60.0_wp + real(frame - 1, wp) * 360.0_wp / real(N_FRAMES, wp)

        call pfig%clear()
        call view_init(elev=30.0_wp, azim=azim)
        call add_3d_plot(xs, ys, zs, label="Lissajous 3D", linestyle='-')
        call xlabel("X")
        call ylabel("Y")
        call title("Rotating 3D Lissajous")
        call set_fixed_limits()
        call pfig%set_rendered(.false.)
    end subroutine rotate_view

    subroutine report_status(label, st)
        character(len=*), intent(in) :: label
        integer, intent(in) :: st

        select case (st)
        case (0)
            print *, label // ": OK"
        case (-1)
            print *, label // ": ffmpeg not found (.mp4 needs ffmpeg)"
        case (-3)
            print *, label // ": unsupported file format"
        case default
            print *, label // ": save failed, status=", st
        end select
    end subroutine report_status

end program three_d_animation_demo