vkf_line_segments.f90 Source File


This file depends on

sourcefile~~vkf_line_segments.f90~~EfferentGraph sourcefile~vkf_line_segments.f90 vkf_line_segments.f90 sourcefile~vkf_frames.f90 vkf_frames.f90 sourcefile~vkf_line_segments.f90->sourcefile~vkf_frames.f90 sourcefile~vkf_line_chain.f90 vkf_line_chain.f90 sourcefile~vkf_line_segments.f90->sourcefile~vkf_line_chain.f90 sourcefile~vkf_math.f90 vkf_math.f90 sourcefile~vkf_line_segments.f90->sourcefile~vkf_math.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_math.f90 sourcefile~vkf_follower.f90 vkf_follower.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_layer.f90 vkf_layer.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_layer.f90 sourcefile~vkf_pick.f90 vkf_pick.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_trajectory.f90 vkf_trajectory.f90 sourcefile~vkf_frames.f90->sourcefile~vkf_trajectory.f90 sourcefile~vkf_line_chain.f90->sourcefile~vkf_math.f90 sourcefile~vkf_line_chain.f90->sourcefile~vkf_layer.f90 sourcefile~vkf_follower.f90->sourcefile~vkf_math.f90 sourcefile~vkf_follower.f90->sourcefile~vkf_trajectory.f90 sourcefile~vkf_geometry.f90->sourcefile~vkf_math.f90 sourcefile~vkf_geometry.f90->sourcefile~vkf_layer.f90 sourcefile~vkf_pick.f90->sourcefile~vkf_math.f90 sourcefile~vkf_text.f90->sourcefile~vkf_math.f90 sourcefile~vkf_text.f90->sourcefile~vkf_layer.f90 sourcefile~vkf_files.f90 vkf_files.f90 sourcefile~vkf_text.f90->sourcefile~vkf_files.f90 sourcefile~vkf_trajectory.f90->sourcefile~vkf_math.f90

Files dependent on this one

sourcefile~~vkf_line_segments.f90~~AfferentGraph sourcefile~vkf_line_segments.f90 vkf_line_segments.f90 sourcefile~vkframes.f90 vkframes.f90 sourcefile~vkframes.f90->sourcefile~vkf_line_segments.f90

Source Code

!> Custom line segments, as OpenFrames' CustomLineSegments: a frame drawing
!> line segments whose endpoints and colors (one per endpoint, blended along
!> the segment) are set by the program ([[set_segments]]), or computed each
!> frame by a callback ([[set_callback]]): e.g. lines of sight between
!> spacecraft, or links between ground stations and satellites.
!>
!> The endpoints are in the frame's own coordinates (the frame can move like
!> any other); `position_of(other)` gives another frame's origin in them. The
!> callback, an extension of the abstract LineSegmentsCallback (OpenFrames'
!> CustomLineSegments::Callback), is called once per scene update with the
!> simulation time, after the whole tree has been updated
!> ([[ReferenceFrame:late_update]]), so the positions of other frames are those
!> for that time whatever their place in the tree; it sets the segments with
!> [[set_segments]]. Setting the same segments again uploads nothing.
!>
!> The segments are drawn by a LineChain of separate segments (vkf_line_chain:
!> thick lines in window units, square caps, dash patterns, points kept to
!> double precision), with a color per point (vkf_line_create_colored).
!> Without colors, the segments take the frame's color ([[CustomLineSegments:set_color]]).
!> (OpenFrames' custom line shader, setLineShader, isn't available: it would
!> need GLSL compiled at run time.)
module vkf_line_segments

    use, intrinsic :: iso_c_binding, only: c_ptr, c_associated, c_float
    use vkf_math, only: wp
    use vkf_frames, only: ReferenceFrame
    use vkf_line_chain, only: LineChain, CHAIN_SEGMENTS, split_points

    implicit none

    private

    !> Computes a CustomLineSegments' segments each frame: extend it with a
    !> [[LineSegmentsCallback:compute]] procedure, which calls `segments%set_segments` (OpenFrames'
    !> CustomLineSegments::Callback). The program owns the callback object (it
    !> must stay alive while set).
    type, abstract, public :: LineSegmentsCallback
    contains
        procedure(segments_compute), deferred :: compute
    end type LineSegmentsCallback

    !> Line segments with a color per endpoint (see the module documentation).
    type, extends(ReferenceFrame), public :: CustomLineSegments
        private
        integer :: n = 0                                    !! segments
        real(wp), allocatable :: a(:,:), b(:,:)             !! (3, n): their endpoints (frame coordinates)
        real(c_float), allocatable :: color_a(:,:), color_b(:,:)   !! (4, n), or not allocated: the frame's color
        real(wp) :: width_ = 1.0_wp                         !! line width (window units)
        integer :: pattern_factor = 1                       !! window units per bit of the dash pattern
        integer :: pattern_ = int(z'FFFF')                  !! 16-bit dash pattern (z'FFFF': solid)
        class(LineSegmentsCallback), pointer :: callback => null()  !! computes the segments each frame (not owned)
        logical :: changed = .true.                         !! to write and upload at the next update
        integer :: n_drawn = 0                              !! segments written to the chain (drawn)
        type(LineChain) :: chain                            !! draws the segments (separate, colored)
    contains
        procedure :: set_segments, num_segments, segment, clear_segments, drawn_segment, num_drawn
        procedure :: set_callback, clear_callback
        procedure :: set_line_width, line_width
        procedure :: set_pattern, pattern
        procedure :: set_color => segments_set_color
        procedure :: frame_info => segments_frame_info
        procedure :: bound => segments_bound
        procedure :: build_contents => segments_build_contents
        procedure :: late_update => segments_late_update
    end type CustomLineSegments

    abstract interface
        !> Set `segments`' segments for simulation time t (with set_segments).
        subroutine segments_compute(self, segments, t)
            import :: LineSegmentsCallback, CustomLineSegments, wp
            implicit none
            class(LineSegmentsCallback), intent(inout) :: self
            class(CustomLineSegments), intent(inout) :: segments !! the line segments the callback is set on
            real(wp), intent(in) :: t           !! simulation time (s)
        end subroutine segments_compute
    end interface

    !> The CustomLineSegments constructor: `CustomLineSegments(...)` makes one (see [[new_custom_line_segments]] for its
    !> arguments).
    interface CustomLineSegments
        module procedure new_custom_line_segments
    end interface CustomLineSegments

contains

    !> Line segments named `name`, of `color` (for segments set without
    !> colors; default white) and `width` (window units; default 1).
    function new_custom_line_segments(name, color, width) result(s)
        character(len=*), intent(in) :: name            !! the frame's name
        real(wp), intent(in), optional :: color(:)      !! [r, g, b] or [r, g, b, a] (0 to 1)
        real(wp), intent(in), optional :: width         !! line width (window units)
        type(CustomLineSegments) :: s
        s%ReferenceFrame = ReferenceFrame(name, color)
        if (present(width)) s%width_ = max(0.0_wp, width)
        allocate(s%a(3, 0), s%b(3, 0))
    end function new_custom_line_segments

    !> The segments: from a(:, i) to b(:, i) (frame coordinates), colored
    !> color_a(:, i) at a and color_b(:, i) at b ([r, g, b] or [r, g, b, a]
    !> columns; one of them for both ends; neither: the frame's color). Setting
    !> the same segments again changes nothing (no upload).
    subroutine set_segments(self, a, b, color_a, color_b)
        class(CustomLineSegments), intent(inout) :: self
        real(wp), intent(in) :: a(:,:)                  !! (3, n): first endpoints (frame coordinates)
        real(wp), intent(in) :: b(:,:)                  !! (3, n): second endpoints (frame coordinates)
        real(wp), intent(in), optional :: color_a(:,:)  !! (3 or 4, n): colors at a, [r, g, b(, a)] (0 to 1)
        real(wp), intent(in), optional :: color_b(:,:)  !! (3 or 4, n): colors at b, [r, g, b(, a)] (0 to 1)
        real(c_float), allocatable :: ca(:,:), cb(:,:)
        integer :: n
        n = min(size(a, 2), size(b, 2))
        if (present(color_a) .or. present(color_b)) then
            allocate(ca(4, n), cb(4, n))
            if (present(color_a)) then
                call rgba(color_a, ca)
            else
                call rgba(color_b, ca)
            end if
            if (present(color_b)) then
                call rgba(color_b, cb)
            else
                cb = ca
            end if
        end if
        if (n == self%n .and. same(a(:, 1:n), self%a) .and. same(b(:, 1:n), self%b) .and. &
            (allocated(ca) .eqv. allocated(self%color_a))) then
            if (.not. allocated(ca)) return
            if (all(ca == self%color_a) .and. all(cb == self%color_b)) return
        end if
        self%n = n
        self%a = a(1:3, 1:n)
        self%b = b(1:3, 1:n)
        if (allocated(ca)) then
            call move_alloc(ca, self%color_a)
            call move_alloc(cb, self%color_b)
        else
            if (allocated(self%color_a)) deallocate(self%color_a, self%color_b)
        end if
        self%changed = .true.

    contains

        !> True if x(1:3, :) and y have the same points.
        pure logical function same(x, y)
            real(wp), intent(in) :: x(:,:), y(:,:)          !! points by column (x: 3 or more rows, y: 3)
            same = size(x, 2) == size(y, 2)
            if (same) same = all(x(1:3, :) == y)
        end function same

    end subroutine set_segments

    !> Colors c (3 or 4 rows) into rgba(4, n) (alpha 1 if not given).
    pure subroutine rgba(c, out)
        real(wp), intent(in) :: c(:,:)              !! (3 or 4, n): [r, g, b(, a)] (0 to 1)
        real(c_float), intent(out) :: out(:,:)      !! (4, n): [r, g, b, a]
        integer :: n, m
        n = min(size(c, 2), size(out, 2))
        m = min(4, size(c, 1))
        out = 1.0_c_float
        out(1:m, 1:n) = real(c(1:m, 1:n), c_float)
    end subroutine rgba

    !> Number of segments set.
    pure integer function num_segments(self)
        class(CustomLineSegments), intent(in) :: self
        num_segments = self%n
    end function num_segments

    !> Segment i: its endpoints, and their colors (rgba).
    subroutine segment(self, i, a, b, color_a, color_b)
        class(CustomLineSegments), intent(in) :: self
        integer, intent(in) :: i                            !! 1 to num_segments
        real(wp), intent(out) :: a(3), b(3)                 !! endpoints (frame coordinates)
        real(wp), intent(out), optional :: color_a(4)       !! color at a: [r, g, b, a]
        real(wp), intent(out), optional :: color_b(4)       !! color at b: [r, g, b, a]
        a = self%a(:, i)
        b = self%b(:, i)
        if (present(color_a)) color_a = segment_color(self, self%color_a, i)
        if (present(color_b)) color_b = segment_color(self, self%color_b, i)
    end subroutine segment

    !> Color i of `c` (color_a or color_b), or the frame's color if `c` is not allocated.
    function segment_color(self, c, i) result(rgba4)
        type(CustomLineSegments), intent(in) :: self
        real(c_float), allocatable, intent(in) :: c(:,:)    !! (4, n): [r, g, b, a]
        integer, intent(in) :: i                            !! 1 to num_segments
        real(wp) :: rgba4(4)
        if (allocated(c)) then
            rgba4 = real(c(:, i), wp)
        else
            rgba4 = self%color()
        end if
    end function segment_color

    !> Segment i as drawn (read back from the GPU buffers, for checks): its
    !> endpoints and colors.
    subroutine drawn_segment(self, i, a, b, color_a, color_b)
        class(CustomLineSegments), intent(in) :: self
        integer, intent(in) :: i                            !! 1 to num_drawn
        real(wp), intent(out) :: a(3), b(3)                 !! endpoints (frame coordinates)
        real(wp), intent(out), optional :: color_a(4)       !! color at a: [r, g, b, a]
        real(wp), intent(out), optional :: color_b(4)       !! color at b: [r, g, b, a]
        a = self%chain%point(2 * i - 1)
        b = self%chain%point(2 * i)
        if (present(color_a)) color_a = self%chain%point_color(2 * i - 1)
        if (present(color_b)) color_b = self%chain%point_color(2 * i)
    end subroutine drawn_segment

    !> Segments drawn now (0 until shown and updated).
    pure integer function num_drawn(self)
        class(CustomLineSegments), intent(in) :: self
        num_drawn = self%n_drawn
    end function num_drawn

    !> Remove all segments.
    subroutine clear_segments(self)
        class(CustomLineSegments), intent(inout) :: self
        real(wp) :: none(3, 0)
        call self%set_segments(none, none)
    end subroutine clear_segments

    !> Compute the segments each frame with `callback` (see the module
    !> documentation; it must stay alive while set).
    subroutine set_callback(self, callback)
        class(CustomLineSegments), intent(inout) :: self
        class(LineSegmentsCallback), intent(in), target :: callback !! the callback (not owned)
        self%callback => callback
    end subroutine set_callback

    !> Stop computing the segments with a callback (they stay as last set).
    subroutine clear_callback(self)
        class(CustomLineSegments), intent(inout) :: self
        self%callback => null()
    end subroutine clear_callback

    !> Line width (window units: logical pixels).
    subroutine set_line_width(self, width)
        class(CustomLineSegments), intent(inout) :: self
        real(wp), intent(in) :: width       !! (window units; >= 0)
        self%width_ = max(0.0_wp, width)
        call apply_style(self)
    end subroutine set_line_width

    !> Line width (window units).
    pure real(wp) function line_width(self)
        class(CustomLineSegments), intent(in) :: self
        line_width = self%width_
    end function line_width

    !> Dash pattern: OpenGL's line stipple (16 bits, bit 0 first, each `factor`
    !> window units; 65535: solid), as [[CurveArtist:set_pattern]].
    subroutine set_pattern(self, factor, pattern)
        class(CustomLineSegments), intent(inout) :: self
        integer, intent(in) :: factor       !! window units per bit (>= 1)
        integer, intent(in) :: pattern      !! 16 bits, bit 0 first (65535: solid)
        self%pattern_factor = max(1, factor)
        self%pattern_ = iand(pattern, int(z'FFFF'))
        call apply_style(self)
    end subroutine set_pattern

    !> The dash pattern (see [[set_pattern]]).
    pure subroutine pattern(self, factor, bits)
        class(CustomLineSegments), intent(in) :: self
        integer, intent(out) :: factor      !! window units per bit
        integer, intent(out) :: bits        !! 16 bits, bit 0 first (65535: solid)
        factor = self%pattern_factor
        bits = self%pattern_
    end subroutine pattern

    !> The frame's color: also that of segments set without colors.
    subroutine segments_set_color(self, color)
        class(CustomLineSegments), intent(inout) :: self
        real(wp), intent(in) :: color(:)    !! [r, g, b] or [r, g, b, a] (0 to 1)
        call self%ReferenceFrame%set_color(color)
        if (.not. allocated(self%color_a)) self%changed = .true.
    end subroutine segments_set_color

    !> The kind of frame: 'CustomLineSegments'.
    function segments_frame_info(self) result(info)
        class(CustomLineSegments), intent(in) :: self
        character(len=:), allocatable :: info
        info = 'CustomLineSegments'
    end function segments_frame_info

    !> The segments' bounding sphere (frame coordinates).
    subroutine segments_bound(self, center, radius)
        class(CustomLineSegments), intent(in) :: self
        real(wp), intent(out) :: center(3), radius          !! the bounding sphere, frame coordinates (scene units)
        real(wp) :: lo(3), hi(3)
        center = 0.0_wp
        radius = 0.0_wp
        if (self%n == 0) return
        lo = min(minval(self%a, dim=2), minval(self%b, dim=2))
        hi = max(maxval(self%a, dim=2), maxval(self%b, dim=2))
        center = 0.5_wp * (lo + hi)
        radius = 0.5_wp * norm2(hi - lo)
    end subroutine segments_bound

    !> The chain of separate segments with a color per point, under `group`.
    subroutine segments_build_contents(self, viewer, group)
        class(CustomLineSegments), intent(inout) :: self
        type(c_ptr), intent(in) :: viewer   !! a window's viewer handle (null: headless)
        type(c_ptr), intent(in) :: group    !! the group node to draw under
        call self%chain%init(group, CHAIN_SEGMENTS, colored=.true.)
        call apply_style(self)
        call self%chain%reserve(max(2, 2 * self%n))          ! (compiled with the scene)
        call self%chain%set_compile(c_associated(viewer))
        self%changed = .true.
    end subroutine segments_build_contents

    !> Give the line width and dash pattern to the chain (if built).
    subroutine apply_style(self)
        type(CustomLineSegments), intent(inout) :: self
        if (.not. self%chain%is_built()) return
        ! white: each point's own color shows (shaders/line.vert multiplies them)
        call self%chain%set_style([1.0_wp, 1.0_wp, 1.0_wp, 1.0_wp], self%width_)
        call self%chain%set_pattern(self%pattern_factor, self%pattern_)
    end subroutine apply_style

    !> After the tree's update for time t: the callback's segments (all frames
    !> at their positions for t), written and uploaded if they changed.
    recursive subroutine segments_late_update(self, t)
        class(CustomLineSegments), intent(inout) :: self
        real(wp), intent(in) :: t           !! simulation time (s)
        if (associated(self%callback)) call self%callback%compute(self, t)
        if (self%changed .and. self%chain%is_built()) call write_segments(self)
        call self%ReferenceFrame%late_update(t)
    end subroutine segments_late_update

    !> The segments into the chain: segment i as points 2i-1 (a) and 2i (b),
    !> with their colors; uploaded, and drawn.
    subroutine write_segments(self)
        type(CustomLineSegments), intent(inout) :: self
        real(c_float), dimension(:,:,:), pointer :: p
        real(c_float), dimension(:,:), pointer :: c
        real(wp), allocatable :: x(:,:)
        real(c_float), allocatable :: colors(:,:)
        integer :: n, j, lo, hi
        n = 2 * self%n
        self%changed = .false.
        self%n_drawn = self%n
        if (n == 0) then
            call self%chain%draw(0)
            return
        end if
        allocate(x(3, n), colors(4, n))
        x(:, 1:n:2) = self%a
        x(:, 2:n:2) = self%b
        if (allocated(self%color_a)) then
            colors(:, 1:n:2) = self%color_a
            colors(:, 2:n:2) = self%color_b
        else
            colors = spread(real(self%color(), c_float), 2, n)
        end if
        call self%chain%reserve(n)
        do j = 1, self%chain%num_chunks()
            call self%chain%slice(j, 1, n, lo, hi, p)
            if (.not. associated(p)) cycle
            call split_points(x(:, lo:hi), p)
            call self%chain%color_slice(j, 1, n, lo, hi, c)
            if (associated(c)) c = colors(:, lo:hi)
        end do
        call self%chain%written(1, n)
        call self%chain%draw(n)
    end subroutine write_segments

end module vkf_line_segments