#!/usr/bin/perl -w # tkil@scrye.com 1999-12-01 =pod Problem: given a set of points on a 2-d plane which define a polygon (not necessarily convex), test additional points to see if they are within the convex hull of the set. My first solution: take the first point, and then form a triangle with each of the other points, in sequence. if the point is within any of those triangles, then the point is within the convex hull. (because convex hulls can be simply triangulated in this manner.) too bad it breaks. try 2: take our test point 't' and draw a vector from it to the first point in the set. this will be both a min and a max vector. for each point after the first, compare against the min vectcor to see if the new vector (from t to current point) is further "clockwise" (negative cross product) than the current min vector. if not, see if it is further "counter-clockwise" (positive cross product) than the current max vector. if either vector has changed, compare the vectors against each other; if min cross max has a negative cross product, then the test point is in the hull. =cut use strict; my $DEBUG = 0; sub pv { local $" = ','; return "(@{$_[0]})"; } sub cross { my ($a, $b) = @_; my $rv = $a->[0] * $b->[1] - $a->[1] * $b->[0]; if ($DEBUG) { my $as = pv($a); my $bs = pv($b); print "cross( $as, $bs ) = $rv\n"; } return $rv; } sub diff { my ($a, $b) = @_; my $rv = [ $b->[0] - $a->[0], $b->[1] - $a->[1] ]; if ($DEBUG) { my $as = pv($a); my $bs = pv($b); my $rvs = pv($rv); print "diff( $as, $bs ) = $rvs\n"; } return $rv; } sub dup { return [ @{ $_[0] } ]; } sub equal { my ($a, $b) = @_; my $rv = ( $a->[0] == $b->[0] && $a->[1] == $b->[1] ) ? 1 : 0; if ($DEBUG) { my $as = pv($a); my $bs = pv($b); print "equal( $as, $bs ) = $rv\n"; } return $rv; } sub point_in_set { my $t = shift; my $verts = shift; # get initial min and max vectors. my $p = $verts->[0]; return 1 if equal($t, $p); my $min = diff($t, $p); my $max = dup($min); for $p ( @{ $verts }[1 .. $#$verts] ) { return 1 if equal($t, $p); my $v = diff($t, $p); my $changed = 0; if (cross($min, $v) < 0) { $min = dup($v); $changed = 1; print " setting min=", pv($min), "\n" if $DEBUG; } elsif (cross($max, $v) > 0) { $max = dup($v); $changed = 1; print " setting max=", pv($max), "\n" if $DEBUG; } if ($changed && cross($min, $max) < 0) { return 1; } } print "not in set, min=", pv($min), ", max=", pv($max), "\n\n" if $DEBUG; return 0; } # ============================================================ my @points = ( [ 4, 1 ], [ 0, -2 ], [ 3, 3 ], [ 0, 0 ], [ -4, -1 ], [ -1, 5 ], ); my @test_points = ( [ -2, 2 ], # should be in the set [ -3, 3 ], # shouldn't be in the set. ); foreach my $t (@test_points) { my $rc = point_in_set($t, \@points); my $rc_s = $rc ? "is in" : "is not in"; my $t_s = '(' . join(", ", @$t) . ')'; print "$t_s $rc_s the set.\n"; print "\n" if $DEBUG; } exit 0;