Silverfrost Forums

Welcome to our forums

Intersection of two curves

28 Dec 2023 8:35 #30901

Hello,

Is there a way to calculate the intersections of two different curves given the data of two arrays, (x1, y1) and (x2, y2)?

Thanks Lester

28 Dec 2023 11:30 #30902

Hi Lester,

The problem does not look too difficult. A practical solution would depend on how the curves are defined.

I am understanding you have two arrays > ie tables of points, like: real :: array1(2,n1) and array2(2,n2).

This could imply the curves are approximated by straight lines between the list of points, ie a straight between array1(:,i) and array1(:.i+1).

A solution could be to find the two straight lines:- between array1(:,i) and array1(:.i+1) and between array2(:,j) and array2(:.j+1), that do actually cross within their space.

This is equivalent to finding i,j so that the straight lines do cross. Basically, do i = 1,n1-1 do j = 1,n2-1 ; is this i,j a solution to the intersection of 2 straight lines (high school math) exit end do end do

Alternatively, should the lines be defined by functions f1(x) and f2(x), then the solution could simply be to find the zero of function f1(x) - f2(x)

You could start there and get an approach that generates a solution, then look for a better way ?

My apologies if I have misunderstood your question.

John

28 Dec 2023 12:13 #30903

Hi John

Thanks for the ideas to test. Yes the curves are defined as arrays of x,y values and not functions.

I would think it logical to look for a small region to test an approximation, where a tolerance on (x1-x2, y1-y2) is small enough. I do have a Scilab code that does this, so may try to convert that as a start.

Will keep you posted if I crack this one!

Lester

31 Dec 2023 2:41 #30907

I thought of a different approach to this problem.

My clearwin+ graphics is based on developing a 'hidden line' removal approach when drawing 3D oblects made up of lines and flat surfaces. This is monitored with two 2D arrays of pixel colours and pixel depths.

A different approach to the curve intersection problem could be to draw the two curves on the screen pixel arrays and note which pixels are allocated to the first curve line elements and then for the second curve. For the second curve, it can be noted when the 2 curves use the same pixel, but if not then scan the pixel array for the closest pixels of the two curves.

A different approach for a more general solution.

( I have always been amazed how fast the pixel based hidden line removal approach is. I developed this in about 1995 and increasing processor and memory speeds have just improved the performance ! Even after configuring for 4K screens, although I previously used larger virtual screens when the physical screen resolution was too small for high resolution plots )

8 Jan 2024 1:48 #30942

Just for fun this is what chatGPT came up with, posted as normal text as it kept getting truncated via [code]!

program FindCurveIntersections implicit none integer, parameter :: num_points = 1000 ! Number of points defining each curve real, dimension(num_points) :: x1, y1, x2, y2, intersections_x, intersections_y integer :: i, num_intersections

! Generate random data for curve 1
call generate_random_data(num_points, x1, y1)

! Generate random data for curve 2
call generate_random_data(num_points, x2, y2)

! Find intersections between the curves
num_intersections = find_intersection(x1, y1, x2, y2, num_points, intersections_x, intersections_y)

! Output the intersections found
print *, \'Number of intersections:\', num_intersections
if (num_intersections > 0) then
    print *, \'Intersections (x, y):\'
    do i = 1, num_intersections
        print *, intersections_x(i), intersections_y(i)
    end do
else
    print *, \'No intersections found.\'
end if

end program FindCurveIntersections

subroutine generate_random_data(num_points, x, y) implicit none integer, intent(in) :: num_points real, dimension(num_points), intent(out) :: x, y integer :: i

do i = 1, num_points
    call random_number(x(i))
    call random_number(y(i))
end do

end subroutine

real function find_intersection(x1, y1, x2, y2, num_points, intersections_x, intersections_y) implicit none real, dimension(:), intent(in) :: x1, y1, x2, y2 real, dimension(:), intent(out) :: intersections_x, intersections_y integer, intent(in) :: num_points integer :: i, j, count real :: tolerance tolerance = 1.0e-6 ! Define a tolerance for comparing points

count = 0

do i = 1, num_points - 1
    do j = 1, num_points - 1
        ! Check if line segments (i, i+1) from curve 1 and (j, j+1) from curve 2 intersect
        if (do_line_segments_intersect(x1(i), y1(i), x1(i+1), y1(i+1), x2(j), y2(j), x2(j+1), y2(j+1), tolerance)) then
            count = count + 1
            call interpolate_intersection(x1(i), y1(i), x1(i+1), y1(i+1), x2(j), y2(j), x2(j+1), y2(j+1), &
                                         intersections_x(count), intersections_y(count))
        end if
    end do
end do

find_intersection = count  ! Return the number of intersections found

end function

logical function do_line_segments_intersect(x1, y1, x2, y2, x3, y3, x4, y4, tolerance) implicit none real, intent(in) :: x1, y1, x2, y2, x3, y3, x4, y4, tolerance real :: dx1, dy1, dx2, dy2, delta real :: s, t

dx1 = x2 - x1
dy1 = y2 - y1
dx2 = x4 - x3
dy2 = y4 - y3

delta = dx1 * dy2 - dy1 * dx2

if (abs(delta) < tolerance) then
    do_line_segments_intersect = .false.  ! Lines are parallel or coincident
else
    s = (dx1 * (y1 - y3) - dy1 * (x1 - x3)) / delta
    t = (dx2 * (y1 - y3) - dy2 * (x1 - x3)) / delta

    if (s >= 0.0 .and. s <= 1.0 .and. t >= 0.0 .and. t <= 1.0) then
        do_line_segments_intersect = .true.  ! Segments intersect
    else
        do_line_segments_intersect = .false.  ! Segments do not intersect
    end if
end if

end function

subroutine interpolate_intersection(x1, y1, x2, y2, x3, y3, x4, y4, intersection_x, intersection_y) implicit none real, intent(in) :: x1, y1, x2, y2, x3, y3, x4, y4 real, intent(out) :: intersection_x, intersection_y real :: dx1, dy1, dx2, dy2, s

dx1 = x2 - x1
dy1 = y2 - y1
dx2 = x4 - x3
dy2 = y4 - y3

s = ((x3 - x1) * dy1 * dx2 + y1 * dx1 * dx2 - y3 * dx2 * dx1) / (dy2 * dx1 - dy1 * dx2)

intersection_x = x3 + s * dx2
intersection_y = y3 + s * dy2

end subroutine

Lester

9 Jan 2024 9:47 #30947

My first attempt:

winapp
program intersect_between_curves
USE clrwin
integer, parameter :: n=301
integer :: i, j, n_intersects
real :: x1(n), y1(n), x2(n), y2(n), th1, a, dth1, xi, yi
real, allocatable :: xip(:), yip(:) 
logical :: intersects
! generate x-y data arrays,  x1,y1 defines a Fermant spiral
  a    = 1.0 ; th1  = 0.0 ; dth1 = 4.0*atan(1.0)/25.0
  do i = 1, n, 1
    x1(i) = a * sqrt(th1) * cos(th1)  ; y1(i) = a * sqrt(th1) * sin(th1)  ; th1 = th1 + dth1
  end do
! x2,y2 is the same spiral displaced by +1 in the x and y directions
  x2 = x1 + 1.0  ; y2 = y1 + 1.0
  
! test each possible pair of line segments from the two curves for intersection
  open(unit=10,status='scratch',form='unformatted')
  n_intersects = 0         
  do i = 1, n-1, 1
    do j = 1, n-1, 1
      call LIP(x1(i), y1(i), x1(i+1), y1(i+1), x2(j), y2(j), x2(j+1), y2(j+1), intersects, xi, yi)
      if (intersects) then
        write(10) xi, yi
        n_intersects = n_intersects + 1
      end if
    end do
  end do

! copy intersection points from scratch file to arrays xip and yip  
  if (allocated(xip)) deallocate(xip)
  if (allocated(yip)) deallocate(yip)
  allocate(xip(n_intersects),yip(n_intersects))
  rewind 10
  do i = 1, n_intersects, 1
    read(10) xip(i), yip(i)
    write(6,'('(',F6.3,',',F6.3,')')') xip(i), yip(i)
  end do
  close(unit=10)
  
! configure plot
  call winop@('%pl[native,n_graphs=3,independent,frame,gridlines,smoothing=4,x_array,x_min=-6]')
  call winop@('%pl[link=lines,colour=red,symbol=0,link=lines,colour=blue,symbol=0,link=none,colour=black,symbol=6]')
  i = winio@('%fn[consolas]%ts%bf&',1.5d0)
  i = winio@('%pl',900,900,[n,n,n_intersects],dble(x1),dble(y1),dble(x2),dble(y2),dble(xip),dble(yip))
end program intersect_between_curves

subroutine LIP(x1, y1, x2, y2, x3, y3, x4, y4, intersects, xi, yi)
! LIP 'Line Intersection Point'
! Line 1 is from point (x1,y1) to point (x2,y2). Line 2 is from point (x3,y3) to point (x4,y4)
! If lines 1 and 2 intersect, intersects is set to .TRUE. and the coordinates of the intersection
! are returned as (xi,yi), otherwise intersects is set to .FALSE.
real, intent(in) :: x1, y1, x2, y2, x3, y3, x4, y4
logical, intent(out) :: intersects
real, intent(out) :: xi, yi
real :: tol = epsilon(1.0), det, a, b
  intersects = .false.
  det = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
  ! If det is zero lines are parallel
  if (abs(det) .lt. tol) return
  ! calculate intersection point
  a  = (x1 * y2 - y1 * x2)
  b  = (x3 * y4 - y3 * x4)
  xi = (a * (x3 - x4) - (x1 - x2) * b) / det
  yi = (a * (y3 - y4) - (y1 - y2) * b) / det
  ! check if intersection point lies on both line segments
  intersects = ((xi .ge. min(x1, x2) .and. xi .le. max(x1, x2)) .and. (xi .ge. min(x3, x4) .and. xi .le. max(x3, x4)) .and. &
                (yi .ge. min(y1, y2) .and. yi .le. max(y1, y2)) .and. (yi .ge. min(y3, y4) .and. yi .le. max(y3, y4)))
end subroutine LIP
9 Jan 2024 2:35 #30948

Praiseworthy, excellent graphical illustration!

10 Jan 2024 7:45 #30950

Thanks for the suggestions Kenneth, the test code worked great.

Added the routines to my existing code and it works fine to compute the intersections, but having issues getting it plotting symbols at the intersections.

This all works fine:

  ! test each possible pair of line segments from the two curves for intersection
  open(unit=10,status='scratch',form='unformatted')
  n_intersects = 0         
  do i = 1, k-1, 1
    do j = 1, k-1, 1
      call LIP (vec(i), real_part(i), vec(i+1), real_part(i+1), vec(j),&
       imaginary_part(j), vec(j+1), imaginary_part(j+1), intersects, xi, yi)
      if (intersects) then
        write(10) xi, yi
        n_intersects = n_intersects + 1
      end if
    end do
  end do
  
  ! copy intersection points from scratch file to arrays xip and yip 
  if (allocated(xip)) deallocate(xip)
  if (allocated(yip)) deallocate(yip)
  allocate(xip(n_intersects),yip(n_intersects))
  rewind 10
  do i = 1, n_intersects, 1
    read(10) xip(i), yip(i)
    where (yip .lt. 0.01) yip=0.0
    write(6,'('(',F6.3,',',F6.3,')')') xip(i), yip(i)
  end do
  close(unit=10)

Assed in a tolerance aspect to narrow things down to get the non-trivial zero locations.

Tried to add the xip, yip and symbol syntax as you showed in the following and it would compile but either make a blank plot or give a run-time error.

  iw = winio@('%mn[Exit]&','Exit')
  iw = winio@('%mn[Export]&',export_cb)
  iw = winio@('%fn[Times New Roman]%ts%bf&',1.5d0)
  call winop@('%pl[native,x_array,frame,gridlines,width=1.5,link=curves,colour=red,colour=blue]')
  call winop@('%pl[xaxis='Real(t)',yaxis='Zeta(s)']')
  call winop@('%pl[Title='Riemann Zeta function (s = 0.5 + i*t)']')
  call winop@('%pl[dx=10, dy=1]')
  call winop@('%pl[x_min=0,x_max=100]')
  call winop@('%pl[y_min=-3,y_max=5]')
  iw = winio@('%pl&',1400,800,k,dble(vec),dble(real_part),dble(imaginary_part))
  iw = winio@('')

Adding the reference to the symbol in winop and xip and yip in winio as you demonstrated did not seem to work for some reason?

Leaving the code to just show the real and imaginary plot is fine and I get the computed intersections.

Lester

10 Jan 2024 10:50 #30951

Lester,

I suspect your problem is related to the %pl options [n_graphs=*], [x_array] and [independent].

In my example I used all of these. This means that for each curve in the plot an array of values corresponding to the x coordinates and the y coordinates of each curve must be provided in the call to %pl. Thus there are six coordinate arrays passed as arguments to the winio call with %pl (i.e. x1, y1, x2, y2, x3, y3). In this case the number of points in each curve is passed as an array. These can be seen below for [n_graphs=3,x_array,independent].

  i = winio@('%pl',900,900,[n,n,n_intersects],dble(x1),dble(y1),dble(x2),dble(y2),dble(xip),dble(yip))

With the %pl option [x_array] and WITHOUT [independent]. This means that each of the three curves share a common x coordinate array. Thus there are four coordinate arrays passed as arguments to the winio call with %pl (i.e. x_common, y1, y2, y3). In this case the number of points in each curve is the same, and this is passed as a scalar.

You should also include the option [n_graphs=3].

11 Jan 2024 12:34 #30952

Hi Kenneth

Managed to get the plot working, just followed you original syntax:

  iw = winio@('%mn[Exit]&','Exit')
  iw = winio@('%mn[Export]&',export_cb)
  iw = winio@('%fn[Times New Roman]%ts%bf&',1.5d0)
  !call winop@('%pl[native,x_array,frame,gridlines,width=1.5,link=curves,colour=red,colour=blue]')
  call winop@('%pl[native,n_graphs=3,independent,frame,gridlines,smoothing=4,x_array]')
  call winop@('%pl[link=curves,colour=red,symbol=0,link=curves,colour=blue,symbol=0,link=none,colour=black,symbol=6]')
  call winop@('%pl[xaxis='Real(t)',yaxis='Zeta(s)']')
  call winop@('%pl[Title='Riemann Zeta function (s = 0.5 + i*t)']')
  call winop@('%pl[dx=10, dy=1]')
  call winop@('%pl[x_min=0,x_max=100]')
  call winop@('%pl[y_min=-3,y_max=5]')
  !iw = winio@('%pl&',1400,800,k,dble(vec),dble(real_part),dble(imaginary_part))
  iw = winio@('%pl',1400,800,[k,k,n_intersects],dble(vec),dble(real_part),dble(vec),dble(imaginary_part),dble(xip),dble(yip))
  iw = winio@('')

real_part and imaginary_part have the same number of point, but n_intersects has fewer, so using independent as an option worked.

Thanks for the help

Lester

Please login to reply.