14,972,394 members
Articles / Programming Languages / Perl
Article
Posted 6 Feb 2016

13.6K views
3 bookmarked

# Point Inside 3D Convex Polygon in Perl

Rate me:
5.00/5 (1 vote)
6 Feb 2016CPOL
An algorithm to determine if a point is inside a 3D convex polygon for a given polygon vertices in Perl

## Introduction

This is a Perl version of the original C++ algorithm which can be found here.

## Background

Please refer to the original C++ algorithm here.

## Using the Code

The algorithm is wrapped into a Perl module library folder `GeoProc`. The main test program `LASProc` reads point cloud data from a LAS file, then writes all inside points into a new LAS file. A LAS file is able to be displayed with a freeware FugroViewer.

To consume the `GeoProc` module library, prepare a `GeoPolygonProc` object first like this:

PERL
```my \$ptsArray = [\$p1, \$p2, \$p3, \$p4, \$p5, \$p6, \$p7, \$p8];
my \$polygon  = new GeoPolygon(\$ptsArray);
my \$procObj  = new GeoPolygonProc(\$polygon);```

Here is how to check if a point is inside the polygon:

PERL
```if (\$x > \$procObj->{x0} && \$x < \$procObj->{x1} &&
\$y > \$procObj->{y0} && \$x < \$procObj->{y1} &&
\$z > \$procObj->{z0} && \$x < \$procObj->{z1} )
{
if (\$procObj->PointInside3DPolygon(\$x, \$y, \$z))```

Here are all `GeoProc` modules:

#### GeoPoint.pm

PERL
```#!/usr/bin/perl

package GeoPoint;

use strict;
use warning;

sub new
{
my \$class = shift;
my \$self = {
x => shift,
y => shift,
z => shift,
};
bless \$self, \$class;
return \$self;
}

{
my \$p0 = shift;
my \$p1 = shift;
return new GeoPoint(\$p0->{x} + \$p1->{x}, \$p0->{y} + \$p1->{y}, \$p0->{z} + \$p1->{z});
}

sub Subtract
{
my \$p0 = shift;
my \$p1 = shift;
return new GeoPoint(\$p0->{x} - \$p1->{x}, \$p0->{y} - \$p1->{y}, \$p0->{z} - \$p1->{z});
}

1;```

#### GeoVector.pm

PERL
```#!/usr/bin/perl

package GeoVector;

use strict;
use warnings;

sub new
{
my \$class = shift;
my \$self = {
p0 => shift,
p1 => shift,
};
bless \$self, \$class;
return \$self;
}

sub getX
{
my \$self = shift;
return \$self->{p1}->{x} - \$self->{p0}->{x};
}

sub getY
{
my \$self = shift;
return \$self->{p1}->{y} - \$self->{p0}->{y};
}

sub getZ
{
my \$self = shift;
return \$self->{p1}->{z} - \$self->{p0}->{z};
}

sub Multiple
{
my \$u = shift;
my \$v = shift;

my \$x = \$u->getY() * \$v->getZ() - \$u->getZ() * \$v->getY();
my \$y = \$u->getZ() * \$v->getX() - \$u->getX() * \$v->getZ();
my \$z = \$u->getX() * \$v->getY() - \$u->getY() * \$v->getX();

my \$p0 = \$v->{p0};
my \$p1 = \$p0 + new GeoPoint(\$x, \$y, \$z);

return new GeoVector(\$p0, \$p1);
}

1;```

#### GeoPlane.pm

PERL
```#!/usr/bin/perl

package GeoPlane;

use strict;
use warnings;

use GeoVector;

sub new
{
my \$class = shift;
my \$self = {
a => shift,
b => shift,
c => shift,
d => shift,
};
bless \$self, \$class;
return \$self;
}

# Create a GeoPlane from 3 GeoPoint.
# Usage: GeoPlane->Create(\$p0, \$p1, \$p2);
sub Create
{
my \$self = shift; # current object, GeoPlane

my \$p0 = shift; # parameter 1, GeoPoint
my \$p1 = shift; # parameter 2, GeoPoint
my \$p2 = shift; # parameter 3, GeoPoint

my \$v = new GeoVector(\$p0, \$p1);
my \$u = new GeoVector(\$p0, \$p2);
my \$n = \$u * \$v;

my \$a = \$n->getX();
my \$b = \$n->getY();
my \$c = \$n->getZ();
my \$d = -(\$a * \$p0->{x} + \$b * \$p0->{y} + \$c * \$p0->{z});

return new GeoPlane(\$a, \$b, \$c, \$d);
}

sub Negative
{
my \$self = shift; # current object, GeoPlane
return new GeoPlane(-\$self->{a}, -\$self->{b}, -\$self->{c}, -\$self->{d});
}

# Instance method to simulate (GeoPlane * GeoPoint)
# Usage: \$dis = \$pl->Multiple(new GeoPoint(\$p0, \$p1, \$p2));
sub Multiple
{
my \$pl = shift; # current object, GeoPlane
my \$pt = shift; # parameter 1, GeoPoint
return \$pt->{x} * \$pl->{a} + \$pt->{y} * \$pl->{b} + \$pt->{z} * \$pl->{c} + \$pl->{d};
}

1;```

#### GeoPolygon.pm

PERL
```#!/usr/bin/perl

package GeoPolygon;

use strict;
use warnings;

sub new
{
my (\$class, \$pts) = @_;

my \$this = {};

my @pts_ref = @\$pts;

\$this->{n} = scalar @pts_ref;

\$this->{v} = [];

\$this->{idx} = [];

for(my \$i=0; \$i<\$this->{n}; \$i++)
{
\$this->{v}[\$i] = \$pts_ref[\$i];
\$this->{idx}[\$i] = \$i;
}

bless \$this, \$class;
return \$this;
}

1;```

#### GeoFace.pm

PERL
```#!/usr/bin/perl

package GeoFace;

use strict;
use warnings;

sub new
{
my (\$class, \$pts, \$ptsIdx) = @_;

my \$this = {};

my @pts_ref = @\$pts;
my @ptsIdx_ref = @\$ptsIdx;

\$this->{n} = scalar @pts_ref;

\$this->{v} = [];

\$this->{idx} = [];

for(my \$i=0; \$i<\$this->{n}; \$i++)
{
push(@{\$this->{v}}, \$pts_ref[\$i]);
push(@{\$this->{idx}}, \$ptsIdx_ref[\$i]);
}

bless \$this, \$class;
return \$this;
}

1;```

#### Utility.pm

PERL
```#!/usr/bin/perl

package Utility;

use strict;
use warnings;
use experimental 'smartmatch';

use constant false => 0;
use constant true  => 1;

# check if 2D array [[]] arr2d contains 1d array [] arr1d.
sub ContainsArray
{
my (\$this, \$arr2d, \$arr1d) = @_;

my @arr2d_ref = @\$arr2d;
my @arr1d_ref = @\$arr1d;

my \$count = scalar @arr2d_ref;

my @sortedItem = sort { \$a <=> \$b } @arr1d_ref;

for (my \$i = 0; \$i < \$count; \$i++)
{
my \$temp = \$arr2d_ref[\$i];
my @currItem = sort { \$a <=> \$b } @\$temp;
if(@currItem ~~ \@sortedItem)
{
return true;
}
}

return false;
}

1;```

#### GeoPolygonProc.pm

PERL
```#!/usr/bin/perl

package GeoPolygonProc;

use strict;
use warnings;
use Data::Dumper;

use constant false => 0;
use constant true  => 1;

my \$MaxUnitMeasureError = 0.001;

sub new
{
my (\$class, \$polygon) = @_;

my \$this = {};

bless \$this, \$class;

\$this->{polygon} = \$polygon;
\$this->SetBoundary();
\$this->SetMaxError();
\$this->SetFacePlanes();

return \$this;
}

sub SetBoundary
{
my (\$this) = @_;

my \$n = \$this->{polygon}->{n};

my \$v = \$this->{polygon}->{v};

\$this->{x0} = @\$v[0]->{x};
\$this->{y0} = @\$v[0]->{y};
\$this->{z0} = @\$v[0]->{z};
\$this->{x1} = @\$v[0]->{x};
\$this->{y1} = @\$v[0]->{y};
\$this->{z1} = @\$v[0]->{z};

for (my \$i = 1; \$i < \$n; \$i++)
{
if (@\$v[\$i]->{x} < \$this->{x0}) {\$this->{x0} = @\$v[\$i]->{x};}
if (@\$v[\$i]->{y} < \$this->{y0}) {\$this->{y0} = @\$v[\$i]->{y};}
if (@\$v[\$i]->{z} < \$this->{z0}) {\$this->{z0} = @\$v[\$i]->{z};}
if (@\$v[\$i]->{x} > \$this->{x1}) {\$this->{x1} = @\$v[\$i]->{x};}
if (@\$v[\$i]->{y} > \$this->{y1}) {\$this->{y1} = @\$v[\$i]->{y};}
if (@\$v[\$i]->{z} > \$this->{z1}) {\$this->{z1} = @\$v[\$i]->{z};}
}
}

sub SetMaxError
{
my (\$this) = @_;
\$this->{maxDisError} = (abs(\$this->{x0}) + abs(\$this->{x1}) +
abs(\$this->{y0}) + abs(\$this->{y1}) +
abs(\$this->{z0}) + abs(\$this->{z1})) / 6 * \$MaxUnitMeasureError;
}

sub SetFacePlanes
{
my (\$this) = @_;

# GeoFace array
\$this->{Faces} = [];

# GeoPlane array
\$this->{FacePlanes} = [];

\$this->{NumberOfFaces} = 0;

# Polygon vertices array
my \$vertices = \$this->{polygon}->{v};

# number of vertices of polygon
my \$n = \$this->{polygon}->{n};

# vertices indexes 2d array for all faces,
# face index is major dimension, face vertice index is minor dimension
# vertices index is the original index value in the input polygon
my @faceVerticeIndex = ();

# face planes for all faces
my \$fpOutward = [];

for(my \$i=0; \$i< \$n; \$i++)
{
# triangle point #1
my \$p0 = @\$vertices[\$i];

for(my \$j= \$i+1; \$j< \$n; \$j++)
{
# triangle point #2
my \$p1 = @\$vertices[\$j];

for(my \$k = \$j + 1; \$k<\$n; \$k++)
{
# triangle point #3
my \$p2 = @\$vertices[\$k];

my \$trianglePlane = GeoPlane->Create(\$p0, \$p1, \$p2);

my \$onLeftCount = 0;
my \$onRightCount = 0;

# Indexes of points that are in same plane with the face triangle plane
# Integer array
my \$pointInSamePlaneIndex = [];

for(my \$l = 0; \$l < \$n ; \$l++)
{
# any points other than the 3 triangle points
if(\$l != \$i && \$l != \$j && \$l != \$k)
{
my \$pt = @\$vertices[\$l];

my \$dis = \$trianglePlane->Multiple(\$pt);

# next point is in the triangle plane
if(abs(\$dis) < \$this->{maxDisError})
{
push(@\$pointInSamePlaneIndex, \$l);
}
else
{
if(\$dis < 0)
{
\$onLeftCount++;
}
else
{
\$onRightCount++;
}
}
}
}

# This is a face for a CONVEX 3d polygon.
# For a CONCAVE 3d polygon, this maybe not a face.
if(\$onLeftCount == 0 || \$onRightCount == 0)
{
# Integer array
my \$verticeIndexInOneFace = [];

# triangle plane
push(@\$verticeIndexInOneFace, \$i);
push(@\$verticeIndexInOneFace, \$j);
push(@\$verticeIndexInOneFace, \$k);

my \$m = scalar @\$pointInSamePlaneIndex;

# there are other vertices in this triangle plane
if(\$m > 0)
{
for(my \$p = 0; \$p < \$m; \$p++)
{
push(@\$verticeIndexInOneFace, @\$pointInSamePlaneIndex[\$p]);
}
}

# if verticeIndexInOneFace is a new face,
# add it in the faceVerticeIndex list,
# add the trianglePlane in the face plane list fpOutward
if (!Utility->ContainsArray(\@faceVerticeIndex, \$verticeIndexInOneFace))
{
push(@faceVerticeIndex, \$verticeIndexInOneFace);

if (\$onRightCount == 0)
{
push(@\$fpOutward, \$trianglePlane);
}
elsif (\$onLeftCount == 0)
{
push(@\$fpOutward, -\$trianglePlane);
}
}
}
else
{
# possible reasons:
# 1. the plane is not a face of a convex 3d polygon,
#    it is a plane crossing the convex 3d polygon.
# 2. the plane is a face of a concave 3d polygon
}

} # k loop
} # j loop
} # i loop

\$this->{NumberOfFaces} = scalar @faceVerticeIndex;

for (my \$i = 0; \$i < \$this->{NumberOfFaces}; \$i++)
{
my \$facePlane = new GeoPlane(@\$fpOutward[\$i]->{a}, @\$fpOutward[\$i]->{b},
@\$fpOutward[\$i]->{c}, @\$fpOutward[\$i]->{d});

#set FacePlanes
push(@{\$this->{FacePlanes}}, \$facePlane);

#GeoPoint array
my \$v = [];

#Integer array
my \$idx = [];

my \$faceIdx = \$faceVerticeIndex[\$i];
my \$count = scalar @\$faceIdx;

for (my \$j = 0; \$j < \$count; \$j++)
{
push(@\$idx, @\$faceIdx[\$j]);
my \$k = @\$idx[\$j];
my \$pt = new GeoPoint(@\$vertices[\$k]->{x}, @\$vertices[\$k]->{y}, @\$vertices[\$k]->{z});
push(@\$v, \$pt);
}

#set Faces
my \$face = new GeoFace(\$v, \$idx);
push(@{\$this->{Faces}}, \$face);
}
}

# public method to update max error
sub UpdateMaxError
{
my (\$this, \$maxError) = @_;
\$this->{maxDisError} = \$maxError;
}

# public method to check if point is inside 3d polygon
sub PointInside3DPolygon
{
my (\$this, \$x, \$y, \$z) = @_;

my \$pt = new GeoPoint(\$x, \$y, \$z);

for (my \$i = 0; \$i < \$this->{NumberOfFaces}; \$i++)
{
my \$pl = \$this->{FacePlanes}[\$i];

my \$dis = \$pl->Multiple(\$pt);

# If the point is in the same half space with normal vector for any face of the cube,
# then it is outside of the 3D polygon
if (\$dis > 0)
{
return false;
}
}

# If the point is in the opposite half space with normal vector for all 6 faces,
# then it is inside of the 3D polygon
return true;
}

1;```

Here is the main test program.

#### LASProc.pl

PERL
```#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;
use Fcntl qw(:seek);

use lib '../GeoProc';
use GeoPoint;
use GeoVector;
use GeoPlane;
use GeoPolygon;
use GeoFace;
use Utility;
use GeoPolygonProc;

# Create GeoPolygonProc object
my \$p1 = new GeoPoint( - 27.28046,  37.11775,  - 39.03485);
my \$p2 = new GeoPoint( - 44.40014,  38.50727,  - 28.78860);
my \$p3 = new GeoPoint( - 49.63065,  20.24757,  - 35.05160);
my \$p4 = new GeoPoint( - 32.51096,  18.85805,  - 45.29785);
my \$p5 = new GeoPoint( - 23.59142,  10.81737,  - 29.30445);
my \$p6 = new GeoPoint( - 18.36091,  29.07707,  - 23.04144);
my \$p7 = new GeoPoint( - 35.48060,  30.46659,  - 12.79519);
my \$p8 = new GeoPoint( - 40.71110,  12.20689,  - 19.05819);
my \$ptsArray = [\$p1, \$p2, \$p3, \$p4, \$p5, \$p6, \$p7, \$p8];
my \$polygon = new GeoPolygon(\$ptsArray);
my \$procObj = new GeoPolygonProc(\$polygon);

# LAS File Process

my \$rb;
my \$wb;
my \$wt;
my \$value;

my \$rbFileName = "../LASInput/cube.las";
my \$wbFileName = "../LASOutput/cube_inside.las";
my \$wtFileName = "../LASOutput/cube_inside.txt";

open(\$rb, "<", \$rbFileName)
|| die "can't open \$rbFileName: \$!";
binmode(\$rb);

open(\$wb, ">", \$wbFileName)
|| die "can't open \$wbFileName: \$!";
binmode(\$wb);

open(\$wt, ">", \$wtFileName)
|| die "can't open \$wtFileName: \$!";

sysseek(\$rb, 96, 0);

my \$offset_to_point_data_value = unpack('I', \$value);
my \$record_len = unpack('v', \$value);
my \$record_num = unpack('L', \$value);

sysseek(\$rb, 131, 0);

my \$x_scale = unpack('d', \$value);
my \$y_scale = unpack('d', \$value);
my \$z_scale = unpack('d', \$value);
my \$x_offset = unpack('d', \$value);
my \$y_offset = unpack('d', \$value);
my \$z_offset = unpack('d', \$value);

sysseek(\$rb, 0, 0);

syswrite(\$wb, \$value);

my \$insideCount = 0;

my (\$i, \$record_loc, \$xi, \$yi, \$zi, \$x, \$y, \$z);

for(\$i=0; \$i<\$record_num; \$i++)
{
\$record_loc = \$offset_to_point_data_value + \$record_len * \$i;
sysseek(\$rb, \$record_loc, 0);
\$xi = unpack('l', \$value);
\$yi = unpack('l', \$value);
\$zi = unpack('l', \$value);
\$x = \$xi * \$x_scale + \$x_offset;
\$y = \$yi * \$y_scale + \$y_offset;
\$z = \$zi * \$z_scale + \$z_offset;

if (\$x > \$procObj->{x0} && \$x < \$procObj->{x1} &&
\$y > \$procObj->{y0} && \$x < \$procObj->{y1} &&
\$z > \$procObj->{z0} && \$x < \$procObj->{z1} )
{
if (\$procObj->PointInside3DPolygon(\$x, \$y, \$z))
{
sysseek(\$rb, \$record_loc, 0);

# write LAS Point record
syswrite(\$wb, \$value);

# write text LAS Point record
\$wt->printf("%15.6f %15.6f %15.6f\n", \$x, \$y, \$z);

\$insideCount++;
}
}
}

# update record_num in LAS header with actual writing point records number
sysseek(\$wb, 107, 0);
syswrite(\$wb, pack('I', \$insideCount));

\$rb->close();
\$wb->close();
\$wt->close();```

Here is a module test program for all GeoProc module library functions and usages.

#### Test.pl

PERL
```#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;

use lib '../GeoProc';
use GeoPoint;
use GeoVector;
use GeoPlane;
use GeoPolygon;
use GeoFace;
use Utility;
use GeoPolygonProc;

my \$p1 = new GeoPoint( - 27.28046,  37.11775,  - 39.03485);
my \$p2 = new GeoPoint( - 44.40014,  38.50727,  - 28.78860);
my \$p3 = new GeoPoint( - 49.63065,  20.24757,  - 35.05160);
my \$p4 = new GeoPoint( - 32.51096,  18.85805,  - 45.29785);
my \$p5 = new GeoPoint( - 23.59142,  10.81737,  - 29.30445);
my \$p6 = new GeoPoint( - 18.36091,  29.07707,  - 23.04144);
my \$p7 = new GeoPoint( - 35.48060,  30.46659,  - 12.79519);
my \$p8 = new GeoPoint( - 40.71110,  12.20689,  - 19.05819);
my \$ptsArray = [\$p1, \$p2, \$p3, \$p4, \$p5, \$p6, \$p7, \$p8];
my \$polygon = new GeoPolygon(\$ptsArray);

print "GeoPoint Test:\n";
my \$pt = \$p1 + \$p2;
print(Dumper(\$pt));
print \$pt->{x} .",". \$pt->{y} .",". \$pt->{z} ."\n";

print "GeoVector Test:\n";
my \$v1 = new GeoVector(\$p1, \$p2);
my \$v2 = new GeoVector(\$p1, \$p3);
my \$v3 = \$v2 * \$v1;
printf("%.3f, %.3f, %.3f\n", \$v3->getX(), \$v3->getY(), \$v3->getZ());

print "GeoPlane Test:\n";
my \$pl = GeoPlane->Create(\$p1, \$p2, \$p3);
printf("%.3f, %.3f, %.3f, %.3f\n", \$pl->{a}, \$pl->{b}, \$pl->{c}, \$pl->{d});
\$pl = new GeoPlane(1.0, 2.0, 3.0, 4.0);
printf("%.3f, %.3f, %.3f, %.3f\n", \$pl->{a}, \$pl->{b}, \$pl->{c}, \$pl->{d});
\$pl = -\$pl;
printf("%.3f, %.3f, %.3f, %.3f\n", \$pl->{a}, \$pl->{b}, \$pl->{c}, \$pl->{d});
my \$dis = \$pl->Multiple(new GeoPoint(1.0, 2.0, 3.0));
printf("%.3f\n", \$dis);

print "GeoPolygon Test:\n";
for(my \$i=0; \$i<\$polygon->{n}; \$i++)
{
print \$polygon->{idx}[\$i] .": (". \$polygon->{v}[\$i]->{x} .", ".
\$polygon->{v}[\$i]->{y} .", ". \$polygon->{v}[\$i]->{z} .")\n";
}

print "GeoFace Test:\n";
my \$faceVerticesArray = [\$p1, \$p2, \$p3, \$p4];
my \$faceVerticeIndexArray = [1, 2, 3, 4];
my \$face = new GeoFace(\$faceVerticesArray, \$faceVerticeIndexArray);
for(my \$i=0; \$i<\$face->{n}; \$i++)
{
print \$face->{idx}[\$i] .": (". \$face->{v}[\$i]->{x} .", ".
\$face->{v}[\$i]->{y} .", ". \$face->{v}[\$i]->{z} .")\n";
}

print "Utility Test:\n";
my \$arr1 = [1, 2, 3, 4];
my \$arr2 = [4, 5, 6, 7];
my \$arr2d = [];
push(@\$arr2d, \$arr1);
push(@\$arr2d, \$arr2);
my \$arr3 = [2, 3, 1, 4];
my \$arr4 = [2, 3, 1, 5];
my \$b1 = Utility->ContainsArray(\$arr2d, \$arr3);
my \$b2 = Utility->ContainsArray(\$arr2d, \$arr4);
print \$b1 .", ". \$b2 ."\n";

print "GeoPolygonProc Test:\n";
my \$procObj = new GeoPolygonProc(\$polygon);
print \$procObj->{x0} .", ". \$procObj->{x1} .", ". \$procObj->{y0} .", ".
\$procObj->{y1} .", ". \$procObj->{z0} .", ". \$procObj->{z1} ."\n";
printf("%.6f\n", \$procObj->{maxDisError});
\$procObj->UpdateMaxError(0.0125);
printf("%.6f\n", \$procObj->{maxDisError});
print \$procObj->{NumberOfFaces}."\n";
print "Face Planes:\n";
for(my \$i=0; \$i<\$procObj->{NumberOfFaces}; \$i++)
{
my \$facePlane = \$procObj->{FacePlanes}[\$i];
printf("%d%s\n", \$i+1, ": ".\$facePlane->{a}.", ".\$facePlane->{b}.", ".
\$facePlane->{c}.", ".\$facePlane->{d});
}
print "Faces:\n";
for(my \$j=0; \$j<\$procObj->{NumberOfFaces}; \$j++)
{
printf("%s%d:\n", "Face ", \$j + 1);
my \$face = \$procObj->{Faces}[\$j];
for(my \$i=0; \$i<\$face->{n}; \$i++)
{
printf("%d%s\n", \$face->{idx}[\$i], ": (". \$face->{v}[\$i]->{x} .", ".
\$face->{v}[\$i]->{y} .", ". \$face->{v}[\$i]->{z} .")");
}
}
my \$insidePoint = new GeoPoint(-28.411750, 25.794500, -37.969000);
my \$outsidePoint = new GeoPoint(-28.411750, 25.794500, -50.969000);
\$b1 = \$procObj->PointInside3DPolygon(\$insidePoint->{x}, \$insidePoint->{y}, \$insidePoint->{z});
\$b2 = \$procObj->PointInside3DPolygon(\$outsidePoint->{x}, \$outsidePoint->{y}, \$outsidePoint->{z});
print \$b1.", ".\$b2;```

Here is the output of the module test program:

PERL
```GeoPoint Test:
\$VAR1 = bless( {
'x' => '-71.6806',
'z' => '-67.82345',
'y' => '75.62502'
}, 'GeoPoint' );
-71.6806,75.62502,-67.82345
GeoVector Test:
-178.391, 160.814, -319.868
GeoPlane Test:
-178.391, 160.814, -319.868, -23321.631
1.000, 2.000, 3.000, 4.000
-1.000, -2.000, -3.000, -4.000
-18.000
GeoPolygon Test:
0: (-27.28046, 37.11775, -39.03485)
1: (-44.40014, 38.50727, -28.7886)
2: (-49.63065, 20.24757, -35.0516)
3: (-32.51096, 18.85805, -45.29785)
4: (-23.59142, 10.81737, -29.30445)
5: (-18.36091, 29.07707, -23.04144)
6: (-35.4806, 30.46659, -12.79519)
7: (-40.7111, 12.20689, -19.05819)
GeoFace Test:
1: (-27.28046, 37.11775, -39.03485)
2: (-44.40014, 38.50727, -28.7886)
3: (-49.63065, 20.24757, -35.0516)
4: (-32.51096, 18.85805, -45.29785)
Utility Test:
1, 0
GeoPolygonProc Test:
-49.63065, -18.36091, 10.81737, 38.50727, -45.29785, -12.79519
0.029235
0.012500
6
Face Planes:
1: -178.390887365, 160.8136689275, -319.8681191512, -23321.6310778083
2: 104.6099805132, 365.1940004963, 125.2599754664, -5811.8668695958
3: 342.39346482, -27.7903996799999, -204.924901278, 2372.9555463542
4: -342.393647417, 27.7906119191, 204.9249816848, -10372.9631493284
5: -104.609966618, -365.193886771, -125.2600697684, -2188.13771525579
6: 178.3908734698, -160.8139027544, 319.8683017482, 15321.6421625963
Faces:
Face 1:
0: (-27.28046, 37.11775, -39.03485)
1: (-44.40014, 38.50727, -28.7886)
2: (-49.63065, 20.24757, -35.0516)
3: (-32.51096, 18.85805, -45.29785)
Face 2:
0: (-27.28046, 37.11775, -39.03485)
1: (-44.40014, 38.50727, -28.7886)
5: (-18.36091, 29.07707, -23.04144)
6: (-35.4806, 30.46659, -12.79519)
Face 3:
0: (-27.28046, 37.11775, -39.03485)
3: (-32.51096, 18.85805, -45.29785)
4: (-23.59142, 10.81737, -29.30445)
5: (-18.36091, 29.07707, -23.04144)
Face 4:
1: (-44.40014, 38.50727, -28.7886)
2: (-49.63065, 20.24757, -35.0516)
6: (-35.4806, 30.46659, -12.79519)
7: (-40.7111, 12.20689, -19.05819)
Face 5:
2: (-49.63065, 20.24757, -35.0516)
3: (-32.51096, 18.85805, -45.29785)
4: (-23.59142, 10.81737, -29.30445)
7: (-40.7111, 12.20689, -19.05819)
Face 6:
4: (-23.59142, 10.81737, -29.30445)
5: (-18.36091, 29.07707, -23.04144)
6: (-35.4806, 30.46659, -12.79519)
7: (-40.7111, 12.20689, -19.05819)
1, 0```

## Points of Interest

TMTOWTDI means "There's more than one way to do it" according to Wall.

## History

• February 06, 2016: Initial date