Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Document the Polygon class.

  • Loading branch information...
commit 124050cef19aa543ff778342a630b944ccefe8c1 1 parent 8a4fbfa
@jcoglan authored
View
224 site/src/pages/api/polygon.haml
@@ -0,0 +1,224 @@
+= partial 'api_navigation'
+
+:plain
+ <h2>Class: Polygon</h2>
+
+ <p>Sylvester version: 0.2.0 onwards</p>
+
+ <p>Class methods:
+ <a href="#create"><code>create</code></a>
+ </p>
+
+ <p>Instance methods:
+ <a href="#area"><code>area</code></a>,
+ <a href="#centroid"><code>centroid</code></a>,
+ <a href="#contains"><code>contains</code></a>,
+ <a href="#copyvertices"><code>copyVertices</code></a>,
+ <a href="#dup"><code>dup</code></a>,
+ <a href="#hasedgecontaining"><code>hasEdgeContaining</code></a>,
+ <a href="#inspect"><code>inspect</code></a>,
+ <a href="#istriangle"><code>isTriangle</code></a>,
+ <a href="#projectionon"><code>projectionOn</code></a>,
+ <a href="#rotate"><code>rotate</code></a>,
+ <a href="#scale"><code>scale</code></a>,
+ <a href="#totriangles"><code>toTriangles</code></a>,
+ <a href="#translate"><code>translate</code></a>,
+ <a href="#v"><code>v</code></a>
+ </p>
+
+ <h3>Overview</h3>
+
+ <p>The <code>Polygon</code> class is designed to model planar regions whose
+ boundary is defined by a set of coplanar points, in 3 dimensions. It is
+ possible to model polygons containing holes by having the boundary
+ &lsquo;cut in&rsquo; to reach the hole &ndash; see
+ <a href="#create"><code>create()</code></a>.</p>
+
+ <h3>Class methods</h3>
+
+ <a name="create"></a>
+ <h3 class="method">Polygon.create([<span class="arg">v1</span>, <span class="arg">v2</span>, ...]) <span class="version">0.2.0</span></h3>
+
+ <p>Creates a <code>Polygon</code> instance using a set of points
+ <code>v1</code>, <code>v2</code, etc. The points can be 2- or 3-dimensional
+ <a href="/api/vector.html"><code>Vector</code></a>s or arrays, and will be
+ coerced into 3-dimensional <code>Vector</code>s internally.</p>
+
+ <p>For example, this creates a 4&times;4 square in the <span
+ class="math">x-y</span> plane.</p>
+
+ <pre><code>var p = Polygon.create([[0,0],[4,0],[4,4],[0,4]]);</code></pre>
+
+ <p>You can create polygons with holes in them by having the boundary
+ &lsquo;cut in&rsquo; to reach the hole, for example consider this shape:</p>
+
+ <pre><code>3 -- o-----------o
+ | |
+ 2 -- | o---o |
+ | | | |
+ 1 -- | o---o |
+ | |
+ 0 -- o-----------o
+
+ | | | |
+ 0 1 2 3</code></pre>
+
+ <p>This can be modelled by joining the inside boundary to the outside, for
+ example:</p>
+
+ <pre><code>3 -- o-----------o
+ | |
+ 2 -- | o---o |
+ | | | |
+ 1 -- | o---o---o
+ | |
+ 0 -- o-----------o
+
+ | | | |
+ 0 1 2 3</code></pre>
+
+ <p>You can think of this as a polygon with no holes, whose boundary touches
+ itself. You might create this shape like so:</p>
+
+ <pre><code>var p = Polygon.create([
+ [0,0], [3,0], [3,1], [1,1],
+ [1,2], [2,2], [2,1], [3,1],
+ [3,3], [0,3]
+ ]);</code></pre>
+
+ <h3>Instance methods</h3>
+
+ <a name="area"></a>
+ <h3 class="method">area() <span class="version">0.2.0</span></h3>
+
+ <p>Returns the area of the polygon. It correctly accounts for polygons
+ containing holes, for example:</p>
+
+ <pre><code>var square = Polygon.create([[0,0],[4,0],[4,4],[0,4]]);
+
+ square.area() // -> 16
+
+ var ring = Polygon.create([
+ [0,0], [3,0], [3,1], [1,1],
+ [1,2], [2,2], [2,1], [3,1],
+ [3,3], [0,3]
+ ]);
+
+ ring.area() // -> 8</code></pre>
+
+ <a name="centroid"></a>
+ <h3 class="method">centroid() <span class="version">0.2.0</span></h3>
+
+ <p>Returns the centroid, or &lsquo;centre of mass&rsquo; of the polygon as
+ a <a href="/api/vector.html"><code>Vector</code></a>.</p>
+
+ <a name="contains"></a>
+ <h3 class="method">contains(<span class="arg">point</span>) <span class="version">0.2.0</span></h3>
+
+ <p>Returns <code>true</code> iff the given
+ <a href="/api/vector.html"><code>Vector</code></a> <code>point</code> is
+ strictly inside the receiver, not on one of its edges or inside a hole
+ contained within the boundary.</p>
+
+ <pre><code>var ring = Polygon.create([
+ [0,0], [3,0], [3,1], [1,1],
+ [1,2], [2,2], [2,1], [3,1],
+ [3,3], [0,3]
+ ]);
+
+ ring.contains([0.5, 0.5]) // -> true -- strictly inside
+ ring.contains([1.0, 1.0]) // -> false -- on a vertex
+ ring.contains([1.5, 1.5]) // -> false -- in a hole</code></pre>
+
+ <a name="copyvertices"></a>
+ <h3 class="method">copyVertices() <span class="version">0.2.0</span></h3>
+
+ <p>Copies all the <a href="/api/vector.html"><code>Vector</code></a> objects
+ that make the vertices of the receiver. If the receiver was created through
+ <a href="#totriangles"><code>toTriangles()</code></a>, for example, it may
+ be sharing vertex objects with another <code>Polygon</code>, and will be
+ transformed when the other is transformed. Copying the vertices means the
+ receiver can be transformed independently.</p>
+
+ <a name="dup"></a>
+ <h3 class="method">dup() <span class="version">0.2.0</span></h3>
+
+ <p>Returns a copy of the receiver. The vertices themselves are not copied,
+ only the list that defines the polygon boundary. This means that if the
+ original is mutated by changing its vertices, the duplicate will be
+ transformed in the same way. Use
+ <a href="#copyvertices"><code>copyVertices()</code></a> to get a completely
+ new set of vertex objects.</p>
+
+ <a name="hasedgecontaining"></a>
+ <h3 class="method">hasEdgeContaining(<span class="arg">point</span>) <span class="version">0.2.0</span></h3>
+
+ <p>Returns <code>true</code> iff the given
+ <a href="/api/vector.html"><code>Vector</code></a> <code>point</code> lies
+ on any line segment or vertex on the polygon&rsquo;s boundary, including
+ edges used as cuts to form holes.</p>
+
+ <a name="inspect"></a>
+ <h3 class="method">inspect() <span class="version">0.2.0</span></h3>
+
+ <p>Returns a string representation of the receiver, useful for debugging.</p>
+
+ <pre><code>var p = Polygon.create([[0,0],[4,0],[4,4]]);
+ p.inspect() // -> "[0, 0, 0] -> [4, 0, 0] -> [4, 4, 0]"</code></pre>
+
+ <a name="istriangle"></a>
+ <h3 class="method">isTriangle() <span class="version">0.2.0</span></h3>
+
+ <p>Returns <code>true</code> iff the polygon has exactly 3 vertices.</p>
+
+ <a name="projectionon"></a>
+ <h3 class="method">projectionOn(<span class="arg">plane</span>) <span class="version">0.2.0</span></h3>
+
+ <p>Returns a new <code>Polygon</code> representing the projection of the
+ receiver onto the given <a href="/api/plane.html"><code>Plane</code></a>.</p>
+
+ <a name="rotate"></a>
+ <h3 class="method">rotate(<span class="arg">angle</span>, <span class="arg">axis</span>) <span class="version">0.1.0</span></h3>
+
+ <p>Rotates the receiver by <code>angle</code> radians
+ about the <a href="/api/line.html"><code>Line</code></a> <code>axis</code>.
+ Rotations are performed in a right-handed fashion about the line&rsquo;s
+ direction.</p>
+
+ <p>Note that while some other classes&rsquo; <code>rotate()</code> method
+ returns a new object, this method mutates and returns the receiver.</p>
+
+ <a name="scale"></a>
+ <h3 class="method">scale(<span class="arg">k</span>, <span class="arg">point</span>) <span class="version">0.1.0</span></h3>
+
+ <p>Scales all the vertices in the receiver by a factor <code>k</code> relative
+ to the <a href="/api/vector.html"><code>Vector</code></a>
+ <code>point</code>.</p>
+
+ <p>Note that while some other classes&rsquo; <code>scale()</code> method
+ returns a new object, this method mutates and returns the receiver.</p>
+
+ <a name="totriangles"></a>
+ <h3 class="method">toTriangles() <span class="version">0.2.0</span></h3>
+
+ <p>Splits the receiver into triangles and returns them as an array of
+ triangular <code>Polygon</code>s. These polygons share their vertex
+ <code>Vector</code> objects with the receiver, so for example if you call
+ <code>toTriangles()</code> and then
+ <a href="#rotate"><code>rotate()</code></a> the receiver, all the triangles
+ will be rotated as well.</p>
+
+ <a name="translate"></a>
+ <h3 class="method">translate(<span class="arg">vector</span>) <span class="version">0.1.0</span></h3>
+
+ <p>Translates the receiver by adding the given <code>vector</code> to all its
+ vertices.</p>
+
+ <p>Note that while some other classes&rsquo; <code>translate()</code> method
+ returns a new object, this method mutates and returns the receiver.</p>
+
+ <a name="v"></a>
+ <h3 class="method">v(<span class="arg">i</span>) <span class="version">0.2.0</span></h3>
+
+ <p>Returns the <a href="/api/vector.html"><code>Vector</code></a> for the
+ <code>i</code>th vertex, numbered from 1.</p>
View
1  site/src/partials/api_navigation.haml
@@ -5,4 +5,5 @@
<li><a href="/api/line.html">Line</a></li>
<li><a href="/api/linesegment.html">Line.Segment</a></li>
<li><a href="/api/plane.html">Plane</a></li>
+ <li><a href="/api/polygon.html">Polygon</a></li>
</ul>
View
74 src/polygon.js
@@ -1,25 +1,18 @@
Sylvester.Polygon = function() {};
Sylvester.Polygon.prototype = {
- // Returns the vertex at the given position on the vertex list, numbered from 1.
v: function(i) {
return this.vertices.at(i - 1).data;
},
- // Returns the node in the vertices linked list that refers to the given vertex.
nodeFor: function(vertex) {
return this.vertices.withData(vertex);
},
- // Returns a new polygon with the same vertices as the receiver. The vertices
- // will not be duplicates, they refer to the same objects as the vertices in this
- // polygon, but the linked list and nodes used to point to them are separate and
- // can be manipulated independently of this one.
dup: function() {
return Sylvester.Polygon.create(this.vertices, this.plane);
},
- // Translates the polygon by the given vector and returns the polygon.
translate: function(vector) {
var P = vector.elements || vector;
this.vertices.each(function(node) {
@@ -31,7 +24,6 @@ Sylvester.Polygon.prototype = {
return this;
},
- // Rotates the polygon about the given line and returns the polygon.
rotate: function(t, line) {
var R = Sylvester.Matrix.Rotation(t, line.direction);
this.vertices.each(function(node) {
@@ -42,7 +34,6 @@ Sylvester.Polygon.prototype = {
return this;
},
- // Scales the polygon relative to the given point and returns the polygon.
scale: function(k, point) {
var P = point.elements || point;
this.vertices.each(function(node) {
@@ -59,14 +50,14 @@ Sylvester.Polygon.prototype = {
return this;
},
- // Updates the plane properties of all the cached triangles belonging to
- // the polygon according to the given function. For example, suppose you
- // just rotated the polygon, you should call:
+ // Updates the plane properties of all the cached triangles belonging to the
+ // polygon according to the given function. For example, suppose you just
+ // rotated the polygon, you should call:
//
// poly.updateTrianglePlanes(function(plane) { return plane.rotate(t, line); });
//
- // This method is called automatically by Sylvester.Polygon.translate, Sylvester.Polygon.rotate
- // and Sylvester.Polygon.scale transformation methods.
+ // This method is called automatically by Sylvester.Polygon.translate,
+ // Sylvester.Polygon.rotate and Sylvester.Polygon.scale transformation methods.
updateTrianglePlanes: function(fn) {
var i;
if (this.cached.triangles !== null) {
@@ -83,19 +74,20 @@ Sylvester.Polygon.prototype = {
}
},
- // Returns true iff the polygon is a triangle
isTriangle: function() {
return this.vertices.length === 3;
},
- // Returns a collection of triangles used for calculating area and center of mass.
- // Some of the triangles will not lie inside the polygon - this collection is essentially
- // a series of itervals in a surface integral, so some are 'negative'. If you want the
- // polygon broken into constituent triangles, use toTriangles(). This method is used
- // because it's much faster than toTriangles().
- // The triangles generated share vertices with the original polygon, so they transform
- // with the polygon. They are cached after first calculation and should remain in sync
- // with changes to the parent polygon.
+ // Returns a collection of triangles used for calculating area and center of
+ // mass. Some of the triangles will not lie inside the polygon - this
+ // collection is essentially a series of itervals in a surface integral, so
+ // some are 'negative'. If you want the polygon broken into constituent
+ // triangles, use toTriangles(). This method is used because it's much faster
+ // than toTriangles().
+ //
+ // The triangles generated share vertices with the original polygon, so they
+ // transform with the polygon. They are cached after first calculation and
+ // should remain in sync with changes to the parent polygon.
trianglesForSurfaceIntegral: function() {
if (this.cached.surfaceIntegralElements !== null) { return this.cached.surfaceIntegralElements; }
var triangles = [];
@@ -104,15 +96,13 @@ Sylvester.Polygon.prototype = {
this.vertices.each(function(node, i) {
if (i < 2) { return; }
var points = [firstVertex, node.prev.data, node.data];
- // If the vertices lie on a straigh line, give the polygon's own plane. If the
- // element has no area, it doesn't matter which way its normal faces.
+ // If the vertices lie on a straigh line, give the polygon's own plane. If
+ // the element has no area, it doesn't matter which way its normal faces.
triangles.push(Sylvester.Polygon.create(points, Sylvester.Plane.fromPoints(points) || plane));
});
return this.setCache('surfaceIntegralElements', triangles);
},
- // Returns the area of the polygon. Requires that the polygon
- // be converted to triangles, so use with caution.
area: function() {
if (this.isTriangle()) {
// Area is half the modulus of the cross product of two sides
@@ -133,8 +123,6 @@ Sylvester.Polygon.prototype = {
}
},
- // Returns the centroid of the polygon. Requires division into
- // triangles - use with caution
centroid: function() {
if (this.isTriangle()) {
var A = this.v(1).elements, B = this.v(2).elements, C = this.v(3).elements;
@@ -153,14 +141,12 @@ Sylvester.Polygon.prototype = {
}
},
- // Returns the polygon's projection on the given plane as another polygon
projectionOn: function(plane) {
var points = [];
this.vertices.each(function(node) { points.push(plane.pointClosestTo(node.data)); });
return Sylvester.Polygon.create(points);
},
- // Removes the given vertex from the polygon as long as it's not triangular.
removeVertex: function(vertex) {
if (this.isTriangle()) { return; }
var node = this.nodeFor(vertex);
@@ -199,12 +185,10 @@ Sylvester.Polygon.prototype = {
return this;
},
- // Returns true iff the point is strictly inside the polygon
contains: function(point) {
return this.containsByWindingNumber(point);
},
- // Returns true iff the given point is strictly inside the polygon using the winding number method
containsByWindingNumber: function(point) {
var P = point.elements || point;
if (!this.plane.contains(P)) { return false; }
@@ -224,8 +208,6 @@ Sylvester.Polygon.prototype = {
return loops !== 0;
},
- // Returns true if the given point lies on an edge of the polygon
- // May cause problems with 'hole-joining' edges
hasEdgeContaining: function(point) {
var P = (point.elements || point);
var success = false;
@@ -235,8 +217,6 @@ Sylvester.Polygon.prototype = {
return success;
},
- // Returns an array of 3-vertex polygons that the original has been split into
- // Stores the first calculation for faster retrieval later on
toTriangles: function() {
if (this.cached.triangles !== null) { return this.cached.triangles; }
return this.setCache('triangles', this.triangulateByEarClipping());
@@ -245,7 +225,8 @@ Sylvester.Polygon.prototype = {
// Implementation of ear clipping algorithm
// Found in 'Triangulation by ear clipping', by David Eberly
// at http://www.geometrictools.com
- // This will not deal with overlapping sections - contruct your polygons sensibly
+ // This will not deal with overlapping sections - contruct your polygons
+ // sensibly
triangulateByEarClipping: function() {
var poly = this.dup(), triangles = [], success, convexNode, mainNode, trig;
while (!poly.isTriangle()) {
@@ -259,8 +240,8 @@ Sylvester.Polygon.prototype = {
trig = Sylvester.Polygon.create([mainNode.data, mainNode.next.data, mainNode.prev.data], this.plane);
// Now test whether any reflex vertices lie within the ear
poly.reflexVertices.each(function(node) {
- // Don't test points belonging to this triangle. node won't be
- // equal to convexNode as node is reflex and vertex is convex.
+ // Don't test points belonging to this triangle. node won't be equal
+ // to convexNode as node is reflex and vertex is convex.
if (node.data !== mainNode.prev.data && node.data !== mainNode.next.data) {
if (trig.contains(node.data) || trig.hasEdgeContaining(node.data)) { success = false; }
}
@@ -274,7 +255,6 @@ Sylvester.Polygon.prototype = {
return triangles;
},
- // Sets the polygon's vertices
setVertices: function(points, plane) {
var pointSet = points.toArray ? points.toArray() : points;
this.plane = (plane && plane.normal) ? plane.dup() : Sylvester.Plane.fromPoints(pointSet);
@@ -292,20 +272,18 @@ Sylvester.Polygon.prototype = {
return this;
},
- // Constructs lists of convex and reflex vertices based on the main vertex list.
populateVertexTypeLists: function() {
this.convexVertices = new Sylvester.LinkedList.Circular();
this.reflexVertices = new Sylvester.LinkedList.Circular();
var self = this;
this.vertices.each(function(node) {
- // Split vertices into convex / reflex groups
- // The Sylvester.LinkedList.Node class wraps each vertex so it can belong to many linked lists.
+ // Split vertices into convex / reflex groups. The
+ // Sylvester.LinkedList.Node class wraps each vertex so it can belong to
+ // many linked lists.
self[node.data.type(self) + 'Vertices'].append(new Sylvester.LinkedList.Node(node.data));
});
},
- // Gives the polygon its own local set of vertex points, allowing it to be
- // transformed independently of polygons it may be sharing vertices with.
copyVertices: function() {
this.clearCache();
this.vertices.each(function(node) {
@@ -314,7 +292,6 @@ Sylvester.Polygon.prototype = {
this.populateVertexTypeLists();
},
- // Clear any cached properties
clearCache: function() {
this.cached = {
triangles: null,
@@ -322,13 +299,11 @@ Sylvester.Polygon.prototype = {
};
},
- // Set cached value and return the value
setCache: function(key, value) {
this.cached[key] = value;
return value;
},
- // Returns a string representation of the polygon's vertices.
inspect: function() {
var points = [];
this.vertices.each(function(node) { points.push(node.data.inspect()); });
@@ -336,7 +311,6 @@ Sylvester.Polygon.prototype = {
}
};
-// Constructor function
Sylvester.Polygon.create = function(points, plane) {
var P = new Sylvester.Polygon();
return P.setVertices(points, plane);
Please sign in to comment.
Something went wrong with that request. Please try again.