vkf_math.f90 Source File


Files dependent on this one

sourcefile~~vkf_math.f90~~AfferentGraph sourcefile~vkf_math.f90 vkf_math.f90 sourcefile~vkf_artists.f90 vkf_artists.f90 sourcefile~vkf_artists.f90->sourcefile~vkf_math.f90 sourcefile~vkf_frames.f90 vkf_frames.f90 sourcefile~vkf_artists.f90->sourcefile~vkf_frames.f90 sourcefile~vkf_line_chain.f90 vkf_line_chain.f90 sourcefile~vkf_artists.f90->sourcefile~vkf_line_chain.f90 sourcefile~vkf_pick.f90 vkf_pick.f90 sourcefile~vkf_artists.f90->sourcefile~vkf_pick.f90 sourcefile~vkf_trajectory.f90 vkf_trajectory.f90 sourcefile~vkf_artists.f90->sourcefile~vkf_trajectory.f90 sourcefile~vkf_clock.f90 vkf_clock.f90 sourcefile~vkf_clock.f90->sourcefile~vkf_math.f90 sourcefile~vkf_diagnostics.f90 vkf_diagnostics.f90 sourcefile~vkf_diagnostics.f90->sourcefile~vkf_math.f90 sourcefile~vkf_follower.f90 vkf_follower.f90 sourcefile~vkf_follower.f90->sourcefile~vkf_math.f90 sourcefile~vkf_follower.f90->sourcefile~vkf_trajectory.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_math.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_follower.f90 sourcefile~vkf_geometry.f90 vkf_geometry.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_geometry.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_pick.f90 sourcefile~vkf_text.f90 vkf_text.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_text.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_trajectory.f90 sourcefile~vkf_geometry.f90->sourcefile~vkf_math.f90 sourcefile~vkf_gui.f90 vkf_gui.f90 sourcefile~vkf_gui.f90->sourcefile~vkf_math.f90 sourcefile~vkf_handles.f90 vkf_handles.f90 sourcefile~vkf_handles.f90->sourcefile~vkf_math.f90 sourcefile~vkf_handles.f90->sourcefile~vkf_artists.f90 sourcefile~vkf_handles.f90->sourcefile~vkf_frames.f90 sourcefile~vkf_handles.f90->sourcefile~vkf_geometry.f90 sourcefile~vkf_handles.f90->sourcefile~vkf_pick.f90 sourcefile~vkf_handles.f90->sourcefile~vkf_trajectory.f90 sourcefile~vkf_hud.f90 vkf_hud.f90 sourcefile~vkf_hud.f90->sourcefile~vkf_math.f90 sourcefile~vkf_hud.f90->sourcefile~vkf_frames.f90 sourcefile~vkf_line_chain.f90->sourcefile~vkf_math.f90 sourcefile~vkf_line_segments.f90 vkf_line_segments.f90 sourcefile~vkf_line_segments.f90->sourcefile~vkf_math.f90 sourcefile~vkf_line_segments.f90->sourcefile~vkf_frames.f90 sourcefile~vkf_line_segments.f90->sourcefile~vkf_line_chain.f90 sourcefile~vkf_model.f90 vkf_model.f90 sourcefile~vkf_model.f90->sourcefile~vkf_math.f90 sourcefile~vkf_model.f90->sourcefile~vkf_frames.f90 sourcefile~vkf_model.f90->sourcefile~vkf_pick.f90 sourcefile~vkf_pick.f90->sourcefile~vkf_math.f90 sourcefile~vkf_shadow.f90 vkf_shadow.f90 sourcefile~vkf_shadow.f90->sourcefile~vkf_math.f90 sourcefile~vkf_shadow.f90->sourcefile~vkf_frames.f90 sourcefile~vkf_sphere.f90 vkf_sphere.f90 sourcefile~vkf_shadow.f90->sourcefile~vkf_sphere.f90 sourcefile~vkf_terrain.f90 vkf_terrain.f90 sourcefile~vkf_shadow.f90->sourcefile~vkf_terrain.f90 sourcefile~vkf_shapes.f90 vkf_shapes.f90 sourcefile~vkf_shapes.f90->sourcefile~vkf_math.f90 sourcefile~vkf_shapes.f90->sourcefile~vkf_artists.f90 sourcefile~vkf_shapes.f90->sourcefile~vkf_frames.f90 sourcefile~vkf_sky.f90 vkf_sky.f90 sourcefile~vkf_sky.f90->sourcefile~vkf_math.f90 sourcefile~vkf_sky.f90->sourcefile~vkf_text.f90 sourcefile~vkf_sphere.f90->sourcefile~vkf_math.f90 sourcefile~vkf_sphere.f90->sourcefile~vkf_frames.f90 sourcefile~vkf_sphere.f90->sourcefile~vkf_geometry.f90 sourcefile~vkf_sphere.f90->sourcefile~vkf_pick.f90 sourcefile~vkf_terrain.f90->sourcefile~vkf_math.f90 sourcefile~vkf_terrain.f90->sourcefile~vkf_frames.f90 sourcefile~vkf_terrain.f90->sourcefile~vkf_pick.f90 sourcefile~vkf_text.f90->sourcefile~vkf_math.f90 sourcefile~vkf_trajectory.f90->sourcefile~vkf_math.f90 sourcefile~vkf_view.f90 vkf_view.f90 sourcefile~vkf_view.f90->sourcefile~vkf_math.f90 sourcefile~vkf_view.f90->sourcefile~vkf_frames.f90 sourcefile~vkf_window.f90 vkf_window.f90 sourcefile~vkf_window.f90->sourcefile~vkf_math.f90 sourcefile~vkf_window.f90->sourcefile~vkf_clock.f90 sourcefile~vkf_window.f90->sourcefile~vkf_diagnostics.f90 sourcefile~vkf_window.f90->sourcefile~vkf_frames.f90 sourcefile~vkf_window.f90->sourcefile~vkf_gui.f90 sourcefile~vkf_window.f90->sourcefile~vkf_handles.f90 sourcefile~vkf_window.f90->sourcefile~vkf_hud.f90 sourcefile~vkf_window.f90->sourcefile~vkf_pick.f90 sourcefile~vkf_window.f90->sourcefile~vkf_sky.f90 sourcefile~vkf_window.f90->sourcefile~vkf_text.f90 sourcefile~vkf_window.f90->sourcefile~vkf_view.f90 sourcefile~vkframes.f90 vkframes.f90 sourcefile~vkframes.f90->sourcefile~vkf_math.f90 sourcefile~vkframes.f90->sourcefile~vkf_artists.f90 sourcefile~vkframes.f90->sourcefile~vkf_clock.f90 sourcefile~vkframes.f90->sourcefile~vkf_diagnostics.f90 sourcefile~vkframes.f90->sourcefile~vkf_follower.f90 sourcefile~vkframes.f90->sourcefile~vkf_frames.f90 sourcefile~vkframes.f90->sourcefile~vkf_gui.f90 sourcefile~vkframes.f90->sourcefile~vkf_handles.f90 sourcefile~vkframes.f90->sourcefile~vkf_hud.f90 sourcefile~vkframes.f90->sourcefile~vkf_line_segments.f90 sourcefile~vkframes.f90->sourcefile~vkf_model.f90 sourcefile~vkframes.f90->sourcefile~vkf_pick.f90 sourcefile~vkframes.f90->sourcefile~vkf_shadow.f90 sourcefile~vkframes.f90->sourcefile~vkf_shapes.f90 sourcefile~vkframes.f90->sourcefile~vkf_sky.f90 sourcefile~vkframes.f90->sourcefile~vkf_sphere.f90 sourcefile~vkframes.f90->sourcefile~vkf_terrain.f90 sourcefile~vkframes.f90->sourcefile~vkf_trajectory.f90 sourcefile~vkframes.f90->sourcefile~vkf_view.f90 sourcefile~vkframes.f90->sourcefile~vkf_window.f90

Source Code

!> Math for vkframes: the working precision, quaternions and 4x4 transforms.
!>
!> Quaternions are `q(4) = [x, y, z, w]` (vector part first, as OpenFrames and
!> OpenSceneGraph store them), unit length for rotations. The rotation of q
!> takes vectors in a frame to its parent frame: v_parent = rotate(q, v_local).
!> Matrices are 4x4, column-major, acting on column vectors.
module vkf_math

    use, intrinsic :: iso_fortran_env, only: real64

    implicit none

    private

    integer, parameter, public :: wp = real64           !! working precision
    real(wp), parameter, public :: pi = acos(-1.0_wp)   !! pi

    real(wp), parameter, public :: quat_identity(4) = [0.0_wp, 0.0_wp, 0.0_wp, 1.0_wp]   !! the identity rotation

    public :: quat_from_axis_angle, quat_multiply, quat_conjugate, quat_normalize
    public :: quat_rotate, quat_to_matrix, quat_from_matrix, quat_slerp, quat_from_vectors
    public :: identity_matrix, translation_matrix, frame_matrix, rigid_inverse, cross
    public :: set_rgba

contains

    !> The rotation by `angle` (radians) about `axis` (need not be unit length).
    pure function quat_from_axis_angle(axis, angle) result(q)
        real(wp), intent(in) :: axis(3)     !! rotation axis (any length; zero: the identity)
        real(wp), intent(in) :: angle       !! (radians)
        real(wp) :: q(4)
        real(wp) :: n
        n = norm2(axis)
        if (n == 0.0_wp) then
            q = quat_identity
        else
            q(1:3) = axis / n * sin(0.5_wp * angle)
            q(4) = cos(0.5_wp * angle)
        end if
    end function quat_from_axis_angle

    !> The product a*b: rotating by b, then by a.
    pure function quat_multiply(a, b) result(q)
        real(wp), intent(in) :: a(4), b(4)              !! quaternions [x, y, z, w]
        real(wp) :: q(4)
        q(1) = a(4) * b(1) + a(1) * b(4) + a(2) * b(3) - a(3) * b(2)
        q(2) = a(4) * b(2) - a(1) * b(3) + a(2) * b(4) + a(3) * b(1)
        q(3) = a(4) * b(3) + a(1) * b(2) - a(2) * b(1) + a(3) * b(4)
        q(4) = a(4) * b(4) - a(1) * b(1) - a(2) * b(2) - a(3) * b(3)
    end function quat_multiply

    !> The conjugate of q: the inverse rotation, for a unit quaternion.
    pure function quat_conjugate(q) result(c)
        real(wp), intent(in) :: q(4)                    !! quaternion [x, y, z, w]
        real(wp) :: c(4)
        c = [-q(1:3), q(4)]
    end function quat_conjugate

    !> q / |q| (the identity for a zero quaternion).
    pure function quat_normalize(q) result(u)
        real(wp), intent(in) :: q(4)                    !! quaternion [x, y, z, w] (any length)
        real(wp) :: u(4)
        real(wp) :: n
        n = norm2(q)
        if (n == 0.0_wp) then
            u = quat_identity
        else
            u = q / n
        end if
    end function quat_normalize

    !> The vector v rotated by the unit quaternion q.
    pure function quat_rotate(q, v) result(r)
        real(wp), intent(in) :: q(4), v(3)              !! unit quaternion [x, y, z, w], and the vector
        real(wp) :: r(3)
        real(wp) :: t(3)
        ! r = v + 2 w (u x v) + 2 u x (u x v), with u = q(1:3), w = q(4)
        t = 2.0_wp * cross(q(1:3), v)
        r = v + q(4) * t + cross(q(1:3), t)
    end function quat_rotate

    !> The 3x3 rotation matrix of the unit quaternion q (columns: the rotated axes).
    pure function quat_to_matrix(q) result(m)
        real(wp), intent(in) :: q(4)                    !! unit quaternion [x, y, z, w]
        real(wp) :: m(3,3)
        real(wp) :: x, y, z, w
        x = q(1); y = q(2); z = q(3); w = q(4)
        m(1,1) = 1.0_wp - 2.0_wp * (y*y + z*z)
        m(2,1) = 2.0_wp * (x*y + z*w)
        m(3,1) = 2.0_wp * (x*z - y*w)
        m(1,2) = 2.0_wp * (x*y - z*w)
        m(2,2) = 1.0_wp - 2.0_wp * (x*x + z*z)
        m(3,2) = 2.0_wp * (y*z + x*w)
        m(1,3) = 2.0_wp * (x*z + y*w)
        m(2,3) = 2.0_wp * (y*z - x*w)
        m(3,3) = 1.0_wp - 2.0_wp * (x*x + y*y)
    end function quat_to_matrix

    !> The unit quaternion of a 3x3 rotation matrix (columns: the rotated
    !> axes; the inverse of quat_to_matrix), by Shepperd's method (from the
    !> largest of w and the vector components, for accuracy).
    pure function quat_from_matrix(m) result(q)
        real(wp), intent(in) :: m(3,3)                  !! rotation matrix (columns: the rotated axes)
        real(wp) :: q(4)
        real(wp) :: t, s
        t = m(1,1) + m(2,2) + m(3,3)
        if (t >= max(m(1,1), m(2,2), m(3,3))) then
            s = 2.0_wp * sqrt(1.0_wp + t)
            q = [(m(3,2) - m(2,3)) / s, (m(1,3) - m(3,1)) / s, (m(2,1) - m(1,2)) / s, 0.25_wp * s]
        else if (m(1,1) >= m(2,2) .and. m(1,1) >= m(3,3)) then
            s = 2.0_wp * sqrt(1.0_wp + m(1,1) - m(2,2) - m(3,3))
            q = [0.25_wp * s, (m(1,2) + m(2,1)) / s, (m(1,3) + m(3,1)) / s, (m(3,2) - m(2,3)) / s]
        else if (m(2,2) >= m(3,3)) then
            s = 2.0_wp * sqrt(1.0_wp - m(1,1) + m(2,2) - m(3,3))
            q = [(m(1,2) + m(2,1)) / s, 0.25_wp * s, (m(2,3) + m(3,2)) / s, (m(1,3) - m(3,1)) / s]
        else
            s = 2.0_wp * sqrt(1.0_wp - m(1,1) - m(2,2) + m(3,3))
            q = [(m(1,3) + m(3,1)) / s, (m(2,3) + m(3,2)) / s, 0.25_wp * s, (m(2,1) - m(1,2)) / s]
        end if
        q = q / norm2(q)
    end function quat_from_matrix

    !> Spherical linear interpolation from `a` (t = 0) to `b` (t = 1), along the
    !> shorter arc; linear when the two are nearly equal. As OpenSceneGraph's
    !> osg::Quat::slerp, which OpenFrames' TrajectoryFollower uses, except that
    !> the linear case is normalized (OSG's isn't: a quaternion a little short of
    !> unit length, its rotation matrix then also scaling, by a millionth for
    !> attitudes 0.25 deg apart, a kilometre at the Moon's distance).
    pure function quat_slerp(t, a, b) result(q)
        real(wp), intent(in) :: t           !! the fraction of the way (0 to 1)
        real(wp), intent(in) :: a(4), b(4)  !! unit quaternions [x, y, z, w] at t = 0 and t = 1
        real(wp) :: q(4)
        real(wp), parameter :: epsilon = 0.00001_wp
        real(wp) :: cos_omega, omega, sin_omega, scale_a, scale_b, to(4)
        cos_omega = dot_product(a, b)
        to = b
        if (cos_omega < 0.0_wp) then
            cos_omega = -cos_omega
            to = -b
        end if
        if (1.0_wp - cos_omega > epsilon) then
            omega = acos(cos_omega)
            sin_omega = sin(omega)
            scale_a = sin((1.0_wp - t) * omega) / sin_omega
            scale_b = sin(t * omega) / sin_omega
        else                                ! (close: linear, then normalized below)
            scale_a = 1.0_wp - t
            scale_b = t
        end if
        q = scale_a * a + scale_b * to
        if (1.0_wp - cos_omega <= epsilon) q = q / norm2(q)   ! (a unit quaternion: a rotation, not also a scale)
    end function quat_slerp

    !> The shortest rotation taking the direction of `from` to that of `to`
    !> (OpenSceneGraph's osg::Quat::makeRotate(from, to)); a half turn about a
    !> perpendicular axis if they are opposite, the identity if either is zero.
    pure function quat_from_vectors(from, to) result(q)
        real(wp), intent(in) :: from(3), to(3)          !! directions (any length)
        real(wp) :: q(4)
        real(wp), parameter :: eps = 1.0e-12_wp
        real(wp) :: u(3), v(3), d, axis(3)
        q = quat_identity
        if (norm2(from) == 0.0_wp .or. norm2(to) == 0.0_wp) return
        u = from / norm2(from)
        v = to / norm2(to)
        d = dot_product(u, v)
        if (d >= 1.0_wp - eps) return
        if (d <= -1.0_wp + eps) then
            axis = cross(u, [1.0_wp, 0.0_wp, 0.0_wp])
            if (norm2(axis) < 1.0e-6_wp) axis = cross(u, [0.0_wp, 1.0_wp, 0.0_wp])
            q = [axis / norm2(axis), 0.0_wp]
            return
        end if
        q = quat_normalize([cross(u, v), 1.0_wp + d])
    end function quat_from_vectors

    !> The inverse of a rigid transform (rotation and translation): [R' -R't].
    pure function rigid_inverse(m) result(inv)
        real(wp), intent(in) :: m(4,4)                  !! rigid transform (rotation and translation)
        real(wp) :: inv(4,4)
        inv = 0.0_wp
        inv(1:3, 1:3) = transpose(m(1:3, 1:3))
        inv(1:3, 4) = -matmul(inv(1:3, 1:3), m(1:3, 4))
        inv(4, 4) = 1.0_wp
    end function rigid_inverse

    !> The 4x4 identity matrix.
    pure function identity_matrix() result(m)
        real(wp) :: m(4,4)
        integer :: k
        m = 0.0_wp
        do k = 1, 4
            m(k, k) = 1.0_wp
        end do
    end function identity_matrix

    !> The 4x4 transform translating by `t`.
    pure function translation_matrix(t) result(m)
        real(wp), intent(in) :: t(3)                    !! translation
        real(wp) :: m(4,4)
        m = identity_matrix()
        m(1:3, 4) = t
    end function translation_matrix

    !> A frame's local-to-parent transform, as OpenFrames' FrameTransform builds
    !> it: translate(position) * rotate(attitude) * scale(scale) * translate(-pivot).
    pure function frame_matrix(position, attitude, scale, pivot) result(m)
        real(wp), intent(in) :: position(3)             !! origin in the parent frame
        real(wp), intent(in) :: attitude(4)             !! quaternion [x, y, z, w]
        real(wp), intent(in), optional :: scale(3)      !! scale along each local axis (default 1)
        real(wp), intent(in), optional :: pivot(3)      !! local point placed at `position` (default 0)
        real(wp) :: m(4,4)
        real(wp) :: r(3,3), s(3), p(3)
        integer :: k
        s = 1.0_wp
        p = 0.0_wp
        if (present(scale)) s = scale
        if (present(pivot)) p = pivot
        r = quat_to_matrix(attitude)
        m = 0.0_wp
        do k = 1, 3
            m(1:3, k) = r(:, k) * s(k)
        end do
        m(1:3, 4) = position - matmul(m(1:3, 1:3), p)
        m(4, 4) = 1.0_wp
    end function frame_matrix

    !> The cross product a x b.
    pure function cross(a, b) result(c)
        real(wp), intent(in) :: a(3), b(3)              !! the vectors
        real(wp) :: c(3)
        c = [a(2) * b(3) - a(3) * b(2), a(3) * b(1) - a(1) * b(3), a(1) * b(2) - a(2) * b(1)]
    end function cross

    !> Set `rgba` from `color`: [r, g, b] (alpha unchanged) or [r, g, b, a].
    pure subroutine set_rgba(rgba, color)
        real(wp), intent(inout) :: rgba(4)                  !! [r, g, b, a]
        real(wp), intent(in) :: color(:)                    !! [r, g, b] or [r, g, b, a] (0 to 1)
        integer :: n
        n = min(4, size(color))
        rgba(1:n) = color(1:n)
    end subroutine set_rgba

end module vkf_math