!> A growing set of points drawn from a chain of GPU buffers ("chunks"), for !> the trajectory artists: a line strip, separate segments (pairs of points), !> markers (one per point) or a ribbon (a translucent surface through pairs of !> points: a quad between each pair and the next). Points are written in place, as (high, low) !> float pairs for double precision (see src/vsg/vkf_layer.cpp: EyeRelative), !> with a strip's distance along it (for dash patterns, see shaders/line.vert; !> measured from the trajectory's points), and only the chunks written to are !> uploaded again. A full chain gets new chunks, each as large as the chain so !> far or as what is needed at once, but at least min_chunk and at most !> max_chunk points, so nothing is ever copied or reallocated, and changing a !> point uploads at most max_chunk points again. In a strip, consecutive chunks !> share their end point so that the line is continuous; in a ribbon, their !> end pair. !> !> Level of detail (strips): seen from far away, a dense trajectory is mostly !> segments shorter than a pixel. For each chunk, the chain keeps the bounding !> sphere of its points and, for strides 2, 4, ... 2^lod_levels, a bound on how !> far the points skipped can be from the segments drawn (every stride-th point !> and the last), and gives them to the renderer (vkf_line_set_lod), which picks !> the stride per view (src/vsg/vkf_layer.cpp: EyeRelative). The bounds are !> built level by level: a segment at stride 2s is within the larger of its two !> halves' bounds, plus its middle point's distance from it, of the points it !> skips (the halves' segments lie within that distance of it). They are !> updated only for the segments the points written change. !> !> Writing points i1 .. i2 (indices along the whole chain): !> !> call chain%reserve(i2) !> do j = 1, chain%num_chunks() !> call chain%slice(j, i1, i2, lo, hi, p) ! p: points lo .. hi of chunk j !> if (.not. associated(p)) cycle !> ... fill p(1:3, :, 1:hi-lo+1) (e.g. with split_points) ... !> end do !> call chain%written(i1, i2) ! measure (strips) and upload !> call chain%draw(i2) module vkf_line_chain use, intrinsic :: iso_c_binding, only: c_ptr, c_null_ptr, c_associated, c_int, c_float, c_double, c_f_pointer use vkf_math, only: wp use vkf_layer, only: check_status, & vkf_line_create, vkf_line_create_colored, vkf_line_colors, vkf_marker_create, vkf_ribbon_create, & vkf_line_points, vkf_line_points_changed, & vkf_line_set_range, vkf_node_add_child, vkf_node_remove_child, vkf_node_delete, & vkf_compile_node, line_set_style, line_set_pattern, line_set_segments, & marker_set_style, vkf_line_set_lod, vkf_line_set_highlight, vkf_line_set_trail, vkf_line_set_dash_mode implicit none private integer, parameter :: min_chunk = 64 !! points in the smallest chunk integer, parameter, public :: max_chunk = 65536 !! points in a chunk made for appending (1.5 MB) integer, parameter :: lod_levels = 12 !! strides up to 2^12 = 4096 (strips) !> what a chain draws integer, parameter, public :: CHAIN_STRIP = 0 !! a line strip integer, parameter, public :: CHAIN_SEGMENTS = 1 !! separate segments (pairs of points) integer, parameter, public :: CHAIN_MARKERS = 2 !! a marker per point integer, parameter, public :: CHAIN_RIBBON = 3 !! a ribbon (a quad between each pair of points and the next) public :: split_points, split_distances, line_buffer !> Error bounds of a strip's segments at one stride (level of detail). type :: LodLevel real(c_float), allocatable :: bound(:) !! (0 : segments - 1) end type LodLevel !> A buffer holding points first .. first+capacity-1 of the chain. type :: LineChunk type(c_ptr) :: line = c_null_ptr !! the line, marker or ribbon node integer :: first = 1 !! the chain index of its first point integer :: capacity = 0 !! number of points it holds ! level of detail (strips) type(LodLevel) :: lod(lod_levels) !! error bounds at strides 2, 4, ... 2^lod_levels real(wp) :: box_lo(3) = 0.0_wp !! bounding box of the points written: lower corner real(wp) :: box_hi(3) = 0.0_wp !! bounding box of the points written: upper corner integer :: n_written = 0 !! points written (the bounds are for them) end type LineChunk !> A chain of point buffers (see the module documentation). type, public :: LineChain private type(c_ptr) :: group = c_null_ptr !! the group node the chunks are under (null: not built) logical :: compile_new = .false. !! chunks made from now on are compiled (the group is shown) type(LineChunk), allocatable :: chunks(:) !! (capacity): the chunks, in chain order integer :: n_chunks = 0 !! number of chunks integer :: kind = CHAIN_STRIP !! what is drawn: a CHAIN_* constant type(c_ptr) :: image = c_null_ptr !! (markers: the image (shape 10)) logical :: colored = .false. !! (lines, markers: a color per point (color_slice)) ! style real(c_float) :: color(4) = 1.0_c_float !! [r, g, b, a] (0 to 1) real(c_float) :: width = 1.0_c_float !! line width, or marker size integer :: shape = 0 !! markers real(c_float) :: radius = 0.0_c_float !! (markers: attenuation radius) integer :: pattern_factor = 1 !! (lines: dash pattern (see line_set_pattern)) integer :: pattern = int(z'FFFF') !! (lines: 16-bit dash pattern (z'FFFF': solid)) logical :: lod = .true. !! (strips: level of detail (else every point drawn)) real(c_float) :: highlight(12) = [1.0_c_float, 1.0_c_float, 1.0_c_float, 1.0_c_float, & 0.0_c_float, 40.0_c_float, 50.0_c_float, 12.0_c_float, & 1.5_c_float, 0.0_c_float, 0.0_c_float, 0.0_c_float] !! (lines: highlight, see vkf_line_set_highlight) real(c_float) :: trail(20) = 0.0_c_float !! (strips: the trail, see vkf_line_set_trail; z = 0: none) integer :: dash_mode = 0 !! (lines: 0 anchored, 1 screen-locked, 2 fixed length) real(wp) :: dash_length = 0.0_wp !! (2: the pattern's length, scene units; 0 from the first view) contains procedure :: init, set_compile, is_built, is_colored, release, detach procedure :: reserve, slice, color_slice, written, draw, draw_range procedure :: set_style, set_pattern, set_marker_style, set_highlight, set_trail, set_dash_mode, style_node procedure :: dash_mode_of, point, point_color, distance, num_chunks procedure :: set_lod, lod_error, chunk_bounds end type LineChain contains !> Draw under `group` (a CHAIN_* kind; markers with `image`, a !> vkf_image_read handle, for the image shape). Chunks made later are !> compiled once set_compile says so. subroutine init(self, group, kind, image, colored) class(LineChain), intent(inout) :: self type(c_ptr), intent(in) :: group !! the group node to draw under integer, intent(in) :: kind !! a CHAIN_* constant type(c_ptr), intent(in), optional :: image !! (markers: a vkf_image_read handle) logical, intent(in), optional :: colored !! (lines, markers: a color per point (color_slice)) self%group = group self%kind = kind self%compile_new = .false. self%image = c_null_ptr if (present(image)) self%image = image self%colored = .false. if (present(colored)) self%colored = colored end subroutine init !> Forget the chunks (after their group was removed from the scene): init !> starts again. subroutine release(self) class(LineChain), intent(inout) :: self integer :: j do j = 1, self%n_chunks call vkf_node_delete(self%chunks(j)%line) end do self%n_chunks = 0 self%group = c_null_ptr self%compile_new = .false. end subroutine release !> Remove the chunks from the group (also while running), and release them. subroutine detach(self) class(LineChain), intent(inout) :: self integer :: j do j = 1, self%n_chunks call check_status(vkf_node_remove_child(self%group, self%chunks(j)%line), 'trajectory artists') end do call self%release() end subroutine detach !> Compile chunks made from now on (for the windows running): once the !> group is shown (not while building, when they are compiled with it). subroutine set_compile(self, compile) class(LineChain), intent(inout) :: self logical, intent(in) :: compile !! true: compile new chunks (once the group is shown) self%compile_new = compile end subroutine set_compile !> True if the chain has a group to draw under (between [[init]] and [[release]]). pure logical function is_built(self) class(LineChain), intent(in) :: self is_built = c_associated(self%group) end function is_built !> For lines and markers, whether each point has its own color (init's `colored`). pure logical function is_colored(self) class(LineChain), intent(in) :: self is_colored = self%colored end function is_colored !> Number of chunks. pure integer function num_chunks(self) class(LineChain), intent(in) :: self num_chunks = self%n_chunks end function num_chunks !> Chunks for at least n points (at least one chunk). Separate segments and !> ribbons: chunks hold whole pairs (n should be even). subroutine reserve(self, n) class(LineChain), intent(inout) :: self integer, intent(in) :: n !! number of points to hold type(LineChunk), allocatable :: tmp(:) type(LineChunk) :: c integer :: covered covered = 0 if (self%n_chunks > 0) covered = self%chunks(self%n_chunks)%first + self%chunks(self%n_chunks)%capacity - 1 do while (self%n_chunks == 0 .or. covered < n) if (self%n_chunks == 0) then c%first = 1 c%capacity = max(min_chunk, min(max_chunk, n)) ! the data so far, up to max_chunk else if (self%kind == CHAIN_STRIP) then c%first = covered ! sharing the previous chunk's last point c%capacity = min(max_chunk, max(min_chunk, covered, n - covered + 1)) else if (self%kind == CHAIN_RIBBON) then c%first = covered - 1 ! sharing the previous chunk's last pair c%capacity = min(max_chunk, max(min_chunk, covered, n - covered + 2)) else c%first = covered + 1 c%capacity = min(max_chunk, max(min_chunk, covered, n - covered)) end if if (self%kind == CHAIN_SEGMENTS .or. self%kind == CHAIN_RIBBON) c%capacity = c%capacity + modulo(c%capacity, 2) if (self%kind == CHAIN_MARKERS) then c%line = vkf_marker_create(int(c%capacity, c_int), self%image, merge(1_c_int, 0_c_int, self%colored)) else if (self%kind == CHAIN_RIBBON) then c%line = vkf_ribbon_create(int(c%capacity, c_int), merge(1_c_int, 0_c_int, self%colored)) else if (self%colored) then c%line = vkf_line_create_colored(int(c%capacity, c_int)) else c%line = vkf_line_create(int(c%capacity, c_int)) end if end if if (.not. c_associated(c%line)) return if (self%kind == CHAIN_SEGMENTS) call check_status(line_set_segments(c%line, .true.), 'trajectory artists') call apply_style(self, c%line) if (self%compile_new) call check_status(vkf_compile_node(c%line), 'trajectory artists') call check_status(vkf_node_add_child(self%group, c%line), 'trajectory artists') if (.not. allocated(self%chunks)) allocate(self%chunks(4)) if (self%n_chunks == size(self%chunks)) then allocate(tmp(2 * self%n_chunks)) call move_chunks(self%chunks(1:self%n_chunks), tmp(1:self%n_chunks)) call move_alloc(tmp, self%chunks) end if self%n_chunks = self%n_chunks + 1 self%chunks(self%n_chunks) = c covered = c%first + c%capacity - 1 end do end subroutine reserve !> The part lo .. hi of points i1 .. i2 that chunk j holds, and its buffer !> p(xyz and distance, high/low, lo:hi) (null if none). subroutine slice(self, j, i1, i2, lo, hi, p) class(LineChain), intent(in) :: self integer, intent(in) :: j, i1, i2 !! the chunk, and points i1 .. i2 (chain indices) integer, intent(out) :: lo, hi !! chain indices (lo > hi: none) real(c_float), dimension(:,:,:), pointer, intent(out) :: p !! (4, 2, hi-lo+1) real(c_float), dimension(:,:,:), pointer :: all p => null() associate (c => self%chunks(j)) lo = max(i1, c%first) hi = min(i2, c%first + c%capacity - 1) if (lo > hi) return call line_buffer(c%line, all) if (associated(all)) p => all(:, :, lo-c%first+1:hi-c%first+1) end associate end subroutine slice !> As [[slice]], for the colors of a colored chain: c(rgba, point) of points !> lo .. hi of chunk j (null if not colored or nothing there). subroutine color_slice(self, j, i1, i2, lo, hi, c) class(LineChain), intent(in) :: self integer, intent(in) :: j, i1, i2 !! the chunk, and points i1 .. i2 (chain indices) integer, intent(out) :: lo, hi !! chain indices (lo > hi: none) real(c_float), dimension(:,:), pointer, intent(out) :: c !! (4, hi-lo+1): [r, g, b, a] real(c_float), dimension(:,:), pointer :: all integer(c_int) :: capacity type(c_ptr) :: data c => null() associate (k => self%chunks(j)) lo = max(i1, k%first) hi = min(i2, k%first + k%capacity - 1) if (lo > hi .or. .not. self%colored) return data = vkf_line_colors(k%line, capacity) if (.not. c_associated(data)) return call c_f_pointer(data, all, [4, int(capacity)]) c => all(:, lo-k%first+1:hi-k%first+1) end associate end subroutine color_slice !> Chunks moved to a new array (their level-of-detail bounds moved, not copied). subroutine move_chunks(from, to) type(LineChunk), intent(inout) :: from(:), to(:) !! the chunks, and their new array (same size) integer :: i, k do i = 1, size(from) to(i)%line = from(i)%line to(i)%first = from(i)%first to(i)%capacity = from(i)%capacity to(i)%box_lo = from(i)%box_lo to(i)%box_hi = from(i)%box_hi to(i)%n_written = from(i)%n_written do k = 1, lod_levels if (allocated(from(i)%lod(k)%bound)) call move_alloc(from(i)%lod(k)%bound, to(i)%lod(k)%bound) end do end do end subroutine move_chunks !> Points i1 .. i2 were written: measure a strip's distances along it (from !> the distance at point i1-1), update its level of detail, and upload the !> chunks holding them. `x`: the points in double precision, x(:, i) for !> chain point i (at least i1-1 .. i2), to measure from; without it, from !> the floats written. (i2 is the last point written in the chain.) subroutine written(self, i1, i2, x) class(LineChain), intent(inout) :: self integer, intent(in) :: i1, i2 !! points written (chain indices) real(wp), intent(in), optional :: x(:,:) !! (3, at least i1-1 .. i2): the points integer :: j, lo, hi do j = 1, self%n_chunks associate (c => self%chunks(j)) lo = max(i1, c%first) hi = min(i2, c%first + c%capacity - 1) if (lo > hi) cycle if (self%kind == CHAIN_STRIP) then call measure(self, j, lo, hi, x) call update_lod(self, j, lo, hi, x) end if call check_status(vkf_line_points_changed(c%line, int(hi - c%first + 1, c_int)), 'trajectory artists') end associate end do end subroutine written !> The distances along the strip of points lo .. hi of chunk j: from the !> previous point (in this chunk; the first point of a chunk after the !> first is the previous chunk's last, measured before), or 0 at point 1. !> From `x` (as in [[written]]) if given, else from the floats written. subroutine measure(self, j, lo, hi, x) type(LineChain), intent(in) :: self integer, intent(in) :: j, lo, hi !! the chunk, and its points lo .. hi (chain indices) real(wp), intent(in), optional :: x(:,:) !! (3, chain points): the points, as in [[written]] real(c_float), dimension(:,:,:), pointer :: p, prev real(wp), allocatable :: xf(:,:), s(:) integer :: k0, k1, k, g call line_buffer(self%chunks(j)%line, p) if (.not. associated(p)) return k1 = hi - self%chunks(j)%first + 1 ! local indices k0 .. k1 k0 = lo - self%chunks(j)%first + 1 if (lo > 1 .and. k0 > 1) k0 = k0 - 1 ! from the point before allocate(s(k0:k1)) if (lo == 1) then s(k0) = 0.0_wp else if (k0 == lo - self%chunks(j)%first + 1) then ! the chunk's first point: shared with the previous call line_buffer(self%chunks(j-1)%line, prev) k = self%chunks(j)%first - self%chunks(j-1)%first + 1 s(k0) = real(prev(4, 1, k), wp) + real(prev(4, 2, k), wp) else s(k0) = real(p(4, 1, k0), wp) + real(p(4, 2, k0), wp) end if if (present(x)) then ! (one pass, no temporary arrays) g = self%chunks(j)%first - 1 ! chain index = g + local index do k = k0 + 1, k1 s(k) = s(k - 1) + norm2(x(:, g + k) - x(:, g + k - 1)) end do else xf = real(p(1:3, 1, k0:k1), wp) + real(p(1:3, 2, k0:k1), wp) s(k0+1:k1) = norm2(xf(:, 2:) - xf(:, :k1-k0), dim=1) do k = k0 + 1, k1 s(k) = s(k - 1) + s(k) end do end if call split_distances(s, p(4, :, k0:k1)) end subroutine measure !> Points lo .. hi of chunk j (a strip's) were written, the last being hi: !> update the bounding box and the error bounds of the segments they change !> at each stride, and give them to the renderer. `x` as in [[written]]. subroutine update_lod(self, j, lo, hi, x) type(LineChain), intent(inout) :: self integer, intent(in) :: j, lo, hi !! the chunk, and its points lo .. hi (chain indices) real(wp), intent(in), optional :: x(:,:) !! (3, chain points): the points, as in [[written]] real(c_float), dimension(:,:,:), pointer :: p real(wp) :: b0, b1, q(3) integer :: n, q0, k, s, m, seg, a, e, mid, i associate (c => self%chunks(j)) call line_buffer(c%line, p) if (.not. associated(p)) return n = hi - c%first + 1 ! points written in the chunk (local 0 .. n-1) c%n_written = n q0 = lo - c%first ! the first one written now do i = q0, n - 1 ! the bounding box q = pt(i) if (i == 0) then c%box_lo = q c%box_hi = q else c%box_lo = min(c%box_lo, q) c%box_hi = max(c%box_hi, q) end if end do do k = 1, lod_levels s = 2**k m = 0 if (n >= 2) m = (n - 2) / s + 1 ! segments at stride s if (m == 0) exit if (.not. allocated(c%lod(k)%bound)) allocate(c%lod(k)%bound(0:(c%capacity - 2) / s)) do seg = max(0, q0 - 1) / s, m - 1 ! the segments the points written change a = seg * s e = min(a + s, n - 1) mid = a + s / 2 b0 = 0.0_wp ! the halves' bounds (none at stride 1) b1 = 0.0_wp if (k > 1) b0 = c%lod(k - 1)%bound(2 * seg) if (mid < e) then ! two halves: also the middle point's distance if (k > 1) b1 = c%lod(k - 1)%bound(2 * seg + 1) c%lod(k)%bound(seg) = real(max(b0, b1) + segment_distance(pt(mid), pt(a), pt(e)), c_float) else c%lod(k)%bound(seg) = real(b0, c_float) end if end do end do end associate call send_lod(self, j) contains !> Local point i of the chunk, in double precision. function pt(i) result(v) integer, intent(in) :: i !! the chunk's point (0-based local index) real(wp) :: v(3) if (present(x)) then v = x(:, self%chunks(j)%first + i) else v = real(p(1:3, 1, i + 1), wp) + real(p(1:3, 2, i + 1), wp) end if end function pt end subroutine update_lod !> Give chunk j's level of detail to the renderer (none if switched off). subroutine send_lod(self, j) type(LineChain), intent(in) :: self integer, intent(in) :: j !! the chunk associate (c => self%chunks(j)) call check_status(vkf_line_set_lod(c%line, 0.5_wp * (c%box_lo + c%box_hi), 0.5_wp * norm2(c%box_hi - c%box_lo), & self%lod_error(j), merge(int(lod_levels, c_int), 0_c_int, self%lod)), 'trajectory artists') end associate end subroutine send_lod !> Level of detail on (the default: seen from far away, a strip is drawn !> from fewer points, within half a pixel of all of them) or off (every !> point drawn). subroutine set_lod(self, enabled) class(LineChain), intent(inout) :: self logical, intent(in) :: enabled !! level of detail on (false: every point drawn) integer :: j self%lod = enabled if (self%kind /= CHAIN_STRIP) return do j = 1, self%n_chunks call send_lod(self, j) end do end subroutine set_lod !> Chunk j's error bounds: how far the points skipped can be from the !> segments drawn at stride 2^k (k = 1 .. lod_levels; 0 where not known). function lod_error(self, j) result(error) class(LineChain), intent(in) :: self integer, intent(in) :: j !! the chunk (outside 1 to the chunks: zeros) real(c_float) :: error(lod_levels) integer :: k, n, m error = 0.0_c_float if (j < 1 .or. j > self%n_chunks) return n = 0 do k = 1, lod_levels if (.not. allocated(self%chunks(j)%lod(k)%bound)) exit if (k == 1) n = self%chunks(j)%n_written m = 0 if (n >= 2) m = (n - 2) / 2**k + 1 if (m > 0) error(k) = maxval(self%chunks(j)%lod(k)%bound(0:m-1)) end do end function lod_error !> Distance from point v to the segment from a to b. pure real(wp) function segment_distance(v, a, b) result(d) real(wp), intent(in) :: v(3), a(3), b(3) !! the point, and the segment's ends real(wp) :: ab(3), t, len2 ab = b - a len2 = dot_product(ab, ab) t = 0.0_wp if (len2 > 0.0_wp) t = max(0.0_wp, min(1.0_wp, dot_product(v - a, ab) / len2)) d = norm2(v - (a + t * ab)) end function segment_distance !> Draw points 1 .. k (no upload). subroutine draw(self, k) class(LineChain), intent(in) :: self integer, intent(in) :: k !! last point drawn (chain index) call self%draw_range(1, k) end subroutine draw !> Draw points i1 .. i2 only (chain indices; none if i2 < i1): e.g. a trace !> of decreasing times, its last points. subroutine draw_range(self, i1, i2) class(LineChain), intent(in) :: self integer, intent(in) :: i1, i2 !! first and last points drawn (chain indices) integer :: j, lo, hi do j = 1, self%n_chunks associate (c => self%chunks(j)) lo = max(i1, c%first) hi = min(i2, c%first + c%capacity - 1) if (hi >= lo) then call check_status(vkf_line_set_range(c%line, int(lo - c%first + 1, c_int), int(hi - lo + 1, c_int)), & 'trajectory artists') else call check_status(vkf_line_set_range(c%line, 1_c_int, 0_c_int), 'trajectory artists') end if end associate end do end subroutine draw_range !> For lines, a dash pattern (see line_set_pattern; z'FFFF' solid). subroutine set_pattern(self, factor, pattern) class(LineChain), intent(inout) :: self integer, intent(in) :: factor !! repeat count of each bit of the pattern integer, intent(in) :: pattern !! 16-bit dash pattern (z'FFFF': solid) integer :: j self%pattern_factor = factor self%pattern = pattern do j = 1, self%n_chunks call apply_style(self, self%chunks(j)%line) end do end subroutine set_pattern !> Color (rgba) and width (window units) of the whole chain. subroutine set_style(self, color, width) class(LineChain), intent(inout) :: self real(wp), intent(in) :: color(4) !! [r, g, b, a] (0 to 1) real(wp), intent(in) :: width !! (window units) integer :: j self%color = real(color, c_float) self%width = real(width, c_float) do j = 1, self%n_chunks call apply_style(self, self%chunks(j)%line) end do end subroutine set_style !> For lines, the highlight (see shaders/line.vert): `flags` (1 pulse, 2 !> arrows; 0 none), its color (rgba), the arrows' spacing and speed (window !> units, and per second), the band's width (window units) and the pulse's !> period (s). subroutine set_highlight(self, flags, color, spacing, speed, band, period) class(LineChain), intent(inout) :: self integer, intent(in) :: flags !! 1 pulse + 2 arrows (0: none) real(wp), intent(in) :: color(4) !! [r, g, b, a] (0 to 1) real(wp), intent(in) :: spacing !! between arrows (window units) real(wp), intent(in) :: speed !! of the arrows (window units per second) real(wp), intent(in) :: band !! width of the band the arrows are drawn in (window units) real(wp), intent(in) :: period !! of the pulse (s) integer :: j self%highlight = real([color, real(flags, wp), spacing, speed, band, period, 0.0_wp, 0.0_wp, 0.0_wp], & c_float) do j = 1, self%n_chunks call apply_style(self, self%chunks(j)%line) end do end subroutine set_highlight !> For strips, the trail (see vkf_line_set_trail: 20 values; the third 0: !> none), given to every chunk (changed every frame the time does: a few !> floats per chunk uploaded). subroutine set_trail(self, values) class(LineChain), intent(inout) :: self real(c_float), intent(in) :: values(20) !! the trail (see vkf_line_set_trail) integer :: j self%trail = values if (self%kind /= CHAIN_STRIP) return do j = 1, self%n_chunks call check_status(vkf_line_set_trail(self%chunks(j)%line, self%trail), 'trajectory artists') end do end subroutine set_trail !> For lines, how the dashes are laid out: 0 anchored to the line (the !> default), 1 screen-locked (a constant length in pixels, following the !> line on screen), 2 a fixed length of the line (strips): `length` scene !> units for the pattern, or if 0 its length on screen when first drawn. subroutine set_dash_mode(self, mode, length) class(LineChain), intent(inout) :: self integer, intent(in) :: mode !! 0 anchored, 1 screen-locked, 2 fixed length real(wp), intent(in) :: length !! (2) the pattern's length (scene units; 0: from the first view) integer :: j self%dash_mode = mode self%dash_length = length do j = 1, self%n_chunks call apply_style(self, self%chunks(j)%line) end do end subroutine set_dash_mode !> The dashes' layout (set_dash_mode). pure integer function dash_mode_of(self) class(LineChain), intent(in) :: self dash_mode_of = self%dash_mode end function dash_mode_of !> Give the chain's style to `line`, a node not in the chain (e.g. a !> curve's trace-mode tail, drawn as part of it). subroutine style_node(self, line) class(LineChain), intent(in) :: self type(c_ptr), intent(in) :: line !! node to style (not in the chain) call apply_style(self, line) end subroutine style_node !> For markers, the color (rgba), size (window units), shape (shaders/marker.frag) !> and attenuation radius (scene units, 0 for none). subroutine set_marker_style(self, color, size, shape, radius) class(LineChain), intent(inout) :: self real(wp), intent(in) :: color(4) !! [r, g, b, a] (0 to 1) real(wp), intent(in) :: size !! (window units) real(wp), intent(in) :: radius !! attenuation radius (scene units; 0: none) integer, intent(in) :: shape !! marker shape (shaders/marker.frag) integer :: j self%color = real(color, c_float) self%width = real(size, c_float) self%shape = shape self%radius = real(radius, c_float) do j = 1, self%n_chunks call apply_style(self, self%chunks(j)%line) end do end subroutine set_marker_style !> Give the chain's style (color, width or size, pattern, highlight, marker shape) to the node `line`. subroutine apply_style(self, line) type(LineChain), intent(in) :: self type(c_ptr), intent(in) :: line !! line or marker node if (self%kind == CHAIN_MARKERS) then call check_status(marker_set_style(line, self%color, self%width, self%shape, self%radius), 'trajectory artists') else call check_status(line_set_style(line, self%color, self%width), 'trajectory artists') call check_status(line_set_pattern(line, self%pattern_factor, self%pattern), 'trajectory artists') if (self%kind /= CHAIN_RIBBON) & call check_status(vkf_line_set_highlight(line, self%highlight), 'trajectory artists') if (self%kind == CHAIN_STRIP) call check_status(vkf_line_set_trail(line, self%trail), 'trajectory artists') if (self%kind /= CHAIN_RIBBON) call check_status(vkf_line_set_dash_mode(line, & int(self%dash_mode, c_int), real(self%dash_length, c_double)), & 'trajectory artists') end if end subroutine apply_style !> Chunk j's points (strips: first .. first + n - 1 of the chain) and the !> bounding box of them (n = 0 if none are measured yet), e.g. to skip a !> chunk when picking. subroutine chunk_bounds(self, j, first, n, lo, hi) class(LineChain), intent(in) :: self integer, intent(in) :: j !! the chunk integer, intent(out) :: first, n !! its first point (chain index), and points written (0: none) real(wp), intent(out) :: lo(3), hi(3) !! the bounding box's corners first = self%chunks(j)%first n = self%chunks(j)%n_written lo = self%chunks(j)%box_lo hi = self%chunks(j)%box_hi end subroutine chunk_bounds !> Point i as written: read back from the buffers (zero if not held). function point(self, i) result(v) class(LineChain), intent(in) :: self integer, intent(in) :: i !! chain index real(wp) :: v(3) real(c_float), dimension(:,:,:), pointer :: p integer :: j v = 0.0_wp do j = 1, self%n_chunks associate (c => self%chunks(j)) if (i < c%first .or. i > c%first + c%capacity - 1) cycle call line_buffer(c%line, p) if (associated(p)) v = real(p(1:3, 1, i-c%first+1), wp) + real(p(1:3, 2, i-c%first+1), wp) return end associate end do end function point !> The color of point i of a colored chain, as stored (rgba; white if not colored). function point_color(self, i) result(rgba) class(LineChain), intent(in) :: self integer, intent(in) :: i !! chain index real(wp) :: rgba(4) real(c_float), dimension(:,:), pointer :: c integer :: j, lo, hi rgba = 1.0_wp do j = 1, self%n_chunks call self%color_slice(j, i, i, lo, hi, c) if (associated(c)) then rgba = real(c(:, 1), wp) return end if end do end function point_color !> A strip's distance along it at point i, as written. function distance(self, i) result(s) class(LineChain), intent(in) :: self integer, intent(in) :: i !! chain index real(wp) :: s real(c_float), dimension(:,:,:), pointer :: p integer :: j s = 0.0_wp do j = 1, self%n_chunks associate (c => self%chunks(j)) if (i < c%first .or. i > c%first + c%capacity - 1) cycle call line_buffer(c%line, p) if (associated(p)) s = real(p(4, 1, i-c%first+1), wp) + real(p(4, 2, i-c%first+1), wp) return end associate end do end function distance !> Points x(3, n) into p(xyz and distance, high/low, n): the floats !> nearest, and the rest (the distances are left as they are). pure subroutine split_points(x, p) real(wp), intent(in) :: x(:,:) !! (3, n) real(c_float), intent(inout) :: p(:,:,:) !! (4, 2, n) p(1:3, 1, :) = real(x, c_float) p(1:3, 2, :) = real(x - real(p(1:3, 1, :), wp), c_float) end subroutine split_points !> Distances s(n) into d(high/low, n). pure subroutine split_distances(s, d) real(wp), intent(in) :: s(:) !! (n) real(c_float), intent(inout) :: d(:,:) !! (2, n) d(1, :) = real(s, c_float) d(2, :) = real(s - real(d(1, :), wp), c_float) end subroutine split_distances !> The point buffer of a line or marker node: (xyz and distance, high/low, !> point). subroutine line_buffer(line, p) type(c_ptr), intent(in) :: line !! line or marker node real(c_float), dimension(:,:,:), pointer, intent(out) :: p !! (4, 2, capacity); null if none integer(c_int) :: capacity type(c_ptr) :: data p => null() data = vkf_line_points(line, capacity) if (c_associated(data)) call c_f_pointer(data, p, [4, 2, int(capacity)]) end subroutine line_buffer end module vkf_line_chain