Quick Reference
SWPolygon is a SketchWave class that represents a closed polygon defined by an ordered array of SWPoint vertices. Any number of vertices from 3 to any practical limit may be used. The polygon automatically computes its perimeter, area (shoelace formula), centroid, convexity, and simplicity (simple vs. complex/self-intersecting). It draws itself through an SWGrid coordinate system and supports dynamic fill/stroke styling, scale-from-centroid, and vertex dragging.
- Design Pattern: Vertex-list polygon (not parametric or composite)
- Internal Structure: Array of SWPoint vertices; centroid maintained as a drawable SWPoint; original-vertex snapshot for scale/reset
- Dependencies: SWPoint, SWColor, SWGrid, p5.js
- Key Features: Arbitrary vertex count (≥ 3), shoelace area, convexity and simplicity detection, fill/stroke styling,
drawOnGrid(), scale-from-centroid, horizontal translation, vertex drag support - Common Uses: Interactive polygon construction, geometric art, irregular shape drawing, classroom polygon explorer
Overview
The SWPolygon class takes an ordered list of SWPoint vertices and draws them as a closed filled/stroked polygon using p5.js's beginShape()/vertex()/endShape(CLOSE) pattern. Vertices are stored by reference, which means external mutations (e.g., dragging a point in the demo) are immediately reflected in subsequent drawOnGrid() calls.
A simple polygon has no two non-adjacent edges that cross each other — the boundary never self-intersects. A complex (self-intersecting) polygon has edges that cross, producing a star-like or butterfly appearance.
isSimple detects this in O(n²) time using a parametric segment-intersection test.
Vertex Storage and Mutation
SWPolygon stores the same SWPoint objects passed to the constructor — it does not deep-copy them. This is intentional: the demo's drag logic mutates pt.x / pt.y directly, and the polygon redraws with updated positions automatically. A separate originalVertices snapshot (plain {x, y} objects) is kept for the scale and reset operations.
Centroid
The centroid is the arithmetic mean of all vertex positions — the geometric center of the vertex set (not the area centroid for irregular polygons). It is stored as a full SWPoint so it can be drawn, labeled, and dragged just like any vertex. The centroid is used as the pivot point for scaleAboutCentroid() and for the demo's spin animation.
Key Capabilities
- Arbitrary Vertex Count: Any polygon from a triangle (3 vertices) upwards
- Live Geometry:
perimeter,area,isConvex, andisSimpleare computed fresh each time they are read — always reflect current vertex positions - Drawable Centroid: Centroid stored as an SWPoint; optional visibility (
showCentroid) - Scale from Centroid:
scaleAboutCentroid(factor)uniformly scales all vertices relative to the centroid without moving it - Horizontal Translation:
horizShiftBy(xInc)shifts every vertex and both original snapshots together - Fill & Stroke: Independent fill color (SWColor with alpha) and stroke color/weight;
strokeWeight = 0suppresses the border completely - Dual Coordinate Systems: Draw in screen pixels (
draw()) or grid user coordinates (drawOnGrid())
Typical Workflow
- Place
SWPointobjects on the canvas (or build them programmatically) - Create fill and stroke
SWColorinstances - Construct
new SWPolygon(vertices, fillColor, options) - Style the centroid point as desired (
myPolygon.centroid.strokeColor = swMedGreenetc.) - Call
myPolygon.drawOnGrid(grid)each frame in the p5draw()loop - After dragging vertices, call
updateCentroid(grid)and optionallyrebuildOriginalsAtScale(scale)
Constructor
new SWPolygon(vertices, fillColor, options)Creates a new SWPolygon. Vertices are stored by reference. An originalVertices snapshot is captured immediately for use by scaleAboutCentroid() and reset(). The centroid SWPoint is built from the vertex mean.
| Parameter | Type | Default | Description |
|---|---|---|---|
vertices |
SWPoint[] | required | Ordered array of SWPoint vertices. Minimum 3. Stored by reference. |
fillColor |
SWColor | required | Fill color for the polygon interior (SWColor with HSB + alpha). |
options.strokeColor |
SWColor | undefined | undefined | Border (outline) color. undefined = no stroke drawn. |
options.strokeWeight |
number | 2 | Border thickness in pixels. Set to 0 to suppress the border entirely. |
options.showVertices |
boolean | true | Whether to draw the SWPoint dots at each vertex position. |
options.showCentroid |
boolean | false | Whether to draw the centroid SWPoint. |
// Triangle with fill and stroke
const v1 = new SWPoint(-4, -3, undefined, 12, swRed, "P1");
const v2 = new SWPoint( 4, -3, undefined, 12, swBlue, "P2");
const v3 = new SWPoint( 0, 4, undefined, 12, swGreen, "P3");
const fill = new SWColor(200, 60, 90, 75, "polyFill");
const border = new SWColor(210, 80, 50, 100, "polyStroke");
const tri = new SWPolygon([v1, v2, v3], fill, {
strokeColor: border,
strokeWeight: 2,
showVertices: true,
showCentroid: true
});
// Pentagon — no border (strokeWeight = 0)
const penta = new SWPolygon(pentaVerts, fill, { strokeWeight: 0 });
// Stroke-only (transparent fill — note: SWPolygon always needs a fillColor object,
// so pass a near-transparent color for a stroke-only look)
const transparent = new SWColor(0, 0, 100, 0, "noFill");
const outline = new SWPolygon(verts, transparent, {
strokeColor: border,
strokeWeight: 3,
showVertices: false
});
Properties
Direct read/write properties. Mutate these to restyle the polygon without recreating it.
vertices SWPoint[]The live array of vertex SWPoints. Stored by reference — mutations to individual points are automatically reflected when the polygon is next drawn. Do not replace the array reference; mutate .x / .y on the SWPoints directly.
myPolygon.vertices[0].x = 5; // reposition first vertex
fillColor SWColorThe fill color for the polygon interior. Replace to change the fill. The alpha channel of the SWColor controls fill transparency.
myPolygon.fillColor = new SWColor(120, 60, 85, 50, "greenFill");
strokeColor SWColor | undefinedThe border (outline) color. Set to undefined or use strokeWeight = 0 to suppress the border.
myPolygon.strokeColor = new SWColor(0, 0, 20, 100, "darkBorder");
myPolygon.strokeColor = undefined; // no border
strokeWeight numberBorder thickness in pixels. A value of 0 suppresses the border entirely (calls noStroke() internally) regardless of the strokeColor setting.
myPolygon.strokeWeight = 4; // thick border
myPolygon.strokeWeight = 0; // no border
showVertices booleanWhether to draw the SWPoint dot markers at each vertex. When true, each vertex's own drawOnGrid() is called after the polygon shape is drawn, so vertices appear on top of the fill.
myPolygon.showVertices = false; // hide vertex dots
showCentroid booleanWhether to draw the centroid SWPoint. The centroid updates live as vertices are dragged. Style the centroid independently via myPolygon.centroid.strokeColor etc.
myPolygon.showCentroid = true;
myPolygon.centroid.strokeColor = swMedGreen;
myPolygon.centroid.strokeWeight = 6;
myPolygon.centroid.setLabel("G");
centroid SWPointThe centroid stored as a full SWPoint — drawable, labelable, and draggable. Its position is the arithmetic mean of all vertex positions. Updated automatically by _refreshCentroid() (called inside scaleAboutCentroid(), horizShiftBy(), and reset()). Call updateCentroid(grid) after externally dragging vertices.
console.log(`Centroid: (${myPolygon.centroid.x.toFixed(2)}, ${myPolygon.centroid.y.toFixed(2)})`);
Computed Getters
Read-only computed properties. Recalculated fresh on every access — always reflect the current vertex positions.
vertexCount numberThe number of vertices in the polygon. Equivalent to vertices.length.
console.log(`${myPolygon.vertexCount}-sided polygon`);
perimeter numberThe total perimeter: the sum of all edge lengths including the closing edge from the last vertex back to the first. Computed using Math.hypot(). Reported in user-space units.
console.log(`Perimeter: ${myPolygon.perimeter.toFixed(2)} units`);
area numberThe interior area computed with the shoelace (Gauss) formula: |∑(xᵢ·yᵣ − xᵣ·yᵢ)| / 2. Returns the absolute value, so vertex winding order (CW vs. CCW) does not matter. For self-intersecting (complex) polygons, this returns the algebraic net area rather than the true visual area.
console.log(`Area: ${myPolygon.area.toFixed(2)} sq. units`);
isConvex booleantrue if no interior angle exceeds 180°. Detected using the cross-product sign test: if all consecutive edge cross-products have the same sign, the polygon is convex. Returns false as soon as any sign change is found (concave vertex). Also returns false for self-intersecting polygons, since they are never convex by definition.
console.log(myPolygon.isConvex ? "Convex" : "Concave");
isSimple booleantrue if no two non-adjacent edges cross each other. Uses a parametric segment-intersection test with strict inequalities so shared adjacent-edge endpoints do not count as intersections. Returns false the moment any crossing is found. O(n²) in vertex count. For a self-intersecting (complex) polygon, this returns false.
console.log(myPolygon.isSimple ? "Simple" : "Complex (self-intersecting)");
Methods
Core Drawing Methods
draw()Draws the polygon using the raw screen-pixel coordinates stored directly in each SWPoint (v.x, v.y). Use this only when working without a SWGrid. For standard canvas use, prefer drawOnGrid().
function draw() {
background(220);
myPolygon.draw(); // SWPoint coords treated as screen pixels
}
drawOnGrid(grid)Maps every vertex through grid.userToScreen(v.x, v.y) and draws the polygon in the SWGrid's coordinate system. This is the standard drawing method. Vertices, centroid, fill, and stroke are all handled automatically based on the current property values.
function draw() {
background(220);
grid.draw();
myPolygon.drawOnGrid(grid);
}
Centroid Methods
updateCentroid(grid)Recomputes the centroid position from the current vertex positions and optionally runs labelProximityCheck(grid) on the centroid label. Call this after externally dragging one or more vertices.
// After dragging a vertex in mouseDragged():
myPolygon.updateCentroid(grid);
Scale and Transform Methods
scaleAboutCentroid(factor)Scales all vertices proportionally from the originalCentroid position by factor. A factor of 1.0 restores original size; 2.0 doubles it; 0.5 halves it. Uses the originalVertices snapshot so repeated calls to the same factor always produce the same result (no drift). The centroid position is preserved.
// Scale slider handler (0.2 → 3.0):
myPolygon.scaleAboutCentroid(parseFloat(scaleSlider.value));
rebuildOriginalsAtScale(scale)Re-anchors the originalVertices snapshot to the current vertex positions (divided back by scale), so the scale slider continues to work correctly after a vertex has been dragged. Call this after a drag operation completes (e.g., in mouseReleased()) when a polygon and scale slider coexist.
// After releasing a dragged vertex:
if (myPolygon) {
myPolygon.rebuildOriginalsAtScale(currentScale);
myPolygon.updateCentroid(grid);
}
horizShiftBy(xInc)Shifts every vertex and both the originalVertices snapshot and originalCentroid by xInc user units in the x direction. Useful for repositioning the entire polygon without recreating it. The centroid SWPoint is also refreshed.
myPolygon.horizShiftBy(2); // move 2 units right
myPolygon.horizShiftBy(-1); // move 1 unit left
Reset Method
reset()Restores all vertices to their stored originalVertices positions (captured at construction or last re-anchored by rebuildOriginalsAtScale()). Also refreshes the centroid. Does not affect fill/stroke colors or the scale slider state.
// Reset button handler:
myPolygon.reset();
Utility Methods
toString()Returns a human-readable summary string including vertex count, perimeter, area, and centroid coordinates.
console.log(myPolygon.toString());
// "SWPolygon(vertices:5, perimeter:28.34, area:19.50, centroid:(0.60,0.80))"
Usage Examples
Example 1: Basic Triangle
let grid;
let tri;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
grid = new SWGrid({ UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10) });
const v1 = new SWPoint(-5, -3, undefined, 10, swRed, "A");
const v2 = new SWPoint( 5, -3, undefined, 10, swBlue, "B");
const v3 = new SWPoint( 0, 5, undefined, 10, swGreen, "C");
const fill = new SWColor(200, 55, 90, 70, "triFill");
const border = new SWColor(210, 80, 50, 100, "triStroke");
tri = new SWPolygon([v1, v2, v3], fill, {
strokeColor: border,
strokeWeight: 2,
showVertices: true,
showCentroid: true
});
// Style centroid
tri.centroid.strokeColor = swMedGreen;
tri.centroid.strokeWeight = 8;
tri.centroid.setLabel("G");
tri.centroid.setLabelAbove(15);
}
function draw() {
background(0, 0, 93);
grid.draw();
tri.drawOnGrid(grid);
}
Example 2: Reading Geometric Properties
// After creating myPolygon:
console.log(`Vertices: ${myPolygon.vertexCount}`);
console.log(`Perimeter: ${myPolygon.perimeter.toFixed(2)}`);
console.log(`Area: ${myPolygon.area.toFixed(2)}`);
console.log(`Centroid: (${myPolygon.centroid.x.toFixed(2)}, ${myPolygon.centroid.y.toFixed(2)})`);
console.log(`Convex: ${myPolygon.isConvex}`);
console.log(`Simple: ${myPolygon.isSimple}`);
Example 3: Scale Slider
let currentScale = 1.0;
// HTML slider calls this:
function scalePolygon(factor) {
if (!myPolygon) return;
currentScale = factor;
myPolygon.scaleAboutCentroid(factor);
redraw();
}
// After user drags a vertex, re-anchor so slider still works:
function mouseReleased() {
if (draggedPoint && myPolygon) {
myPolygon.rebuildOriginalsAtScale(currentScale);
myPolygon.updateCentroid(grid);
}
draggedPoint = null;
}
Example 4: Detecting Simple vs. Complex in Real Time
// In draw() — update a label when edges cross:
function draw() {
background(220);
grid.draw();
myPolygon.drawOnGrid(grid);
// Highlight border red when self-intersecting
if (!myPolygon.isSimple) {
myPolygon.strokeColor = new SWColor(0, 90, 90, 100, "warnStroke");
} else {
myPolygon.strokeColor = originalStrokeColor;
}
}
Example 5: Programmatic Polygon (Regular Pentagon)
// Build a regular pentagon using trigonometry (no SWRegularPolygon needed)
function buildRegularPolygon(cx, cy, radius, sides) {
const pts = [];
for (let i = 0; i < sides; i++) {
const angle = (TWO_PI / sides) * i - HALF_PI; // start at top
pts.push(new SWPoint(
cx + radius * cos(angle),
cy + radius * sin(angle),
undefined, 10, swBlue, `P${i + 1}`
));
}
return pts;
}
const penta = new SWPolygon(
buildRegularPolygon(0, 0, 5, 5),
new SWColor(50, 70, 90, 60, "pentaFill"),
{ strokeColor: new SWColor(50, 80, 50, 100, "pentaStroke"), strokeWeight: 2 }
);
Example 6: Christmas Tree Polygon
// Classic Christmas tree silhouette — a simple 11-vertex polygon
// (stacked triangular tiers + trunk)
function buildChristmasTree() {
const pts = [
// Top tip
new SWPoint( 0, 8),
// Upper tier
new SWPoint( 2, 5), new SWPoint( 1, 5),
new SWPoint( 3, 2), new SWPoint( 2, 2),
new SWPoint( 4.5,-1), new SWPoint(-4.5,-1),
new SWPoint(-2, 2), new SWPoint(-3, 2),
new SWPoint(-1, 5), new SWPoint(-2, 5),
].map((p, i) => new SWPoint(p.x, p.y, undefined, 10, swMedGreen, `T${i+1}`));
return new SWPolygon(pts,
new SWColor(130, 70, 55, 100, "treeFill"),
{ strokeColor: new SWColor(130, 80, 30, 100, "treeBorder"), strokeWeight: 2 }
);
}
Best Practices
1. Vertex Ordering
- Place vertices in a consistent winding order (all CCW or all CW); mixing causes the shoelace area to give unexpected values
- For interactive demos, the order is determined by the sequence in which the user clicks — this is fine as long as the area display is treated as approximate
2. Drag + Scale Coexistence
scaleAboutCentroid()usesoriginalVerticesas its reference, so the scale slider always produces a deterministic result from the original shape- After a vertex drag, call
rebuildOriginalsAtScale(currentScale)to re-anchor the snapshot — otherwise the next scale slider movement will "snap" vertices back to pre-drag positions
// Correct pattern after a vertex drag completes:
myPolygon.rebuildOriginalsAtScale(currentScale);
myPolygon.updateCentroid(grid);
3. strokeWeight = 0 for No Border
- Setting
strokeWeight = 0is the correct way to suppress the border — it callsnoStroke()inside the draw methods - Setting
strokeColor = undefinedalso suppresses the border, but keepingstrokeWeightat 0 is the more explicit choice when using the demo slider (minimum value 0)
4. Complex Polygon Caveats
- The shoelace area gives the algebraic net area for self-intersecting polygons, not the total visual area. For classroom purposes, note that this result is only meaningful for simple polygons.
- The convexity test always returns
falsefor complex polygons (a self-intersecting polygon cannot be convex). - The centroid (vertex mean) of a complex polygon still returns a valid point, but it may lie outside all the visible regions of the shape.
5. Geometric Properties Update Timing
- All getters (
perimeter,area,isConvex,isSimple) recompute on every access — they are safe to call in the draw loop - The centroid SWPoint (
myPolygon.centroid) is not automatically refreshed when you mutate vertices directly; callupdateCentroid(grid)explicitly after vertex drags
Integration with Other SketchWave Classes
Script Loading Order
SWPolygon depends on SWColor, SWPoint, and SWGrid:
<!-- p5.js library -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.js"></script>
<!-- SketchWaveJS classes in dependency order -->
<script src="../shapeClasses/swColor.js"></script>
<script src="../shapeClasses/swPoint.js"></script>
<script src="../shapeClasses/swGrid.js"></script>
<script src="../shapeClasses/swPolygon.js"></script>
<!-- Your sketch -->
<script src="../sketches/yourSketch.js"></script>
Working with SWPoint
SWPolygon uses SWPoint in two ways:
- The
verticesarray holds the polygon's corner points — each SWPoint can have its own color, label, andsetDraggable()state for interactive demos - The
centroidproperty is also a full SWPoint, built from the vertex mean, with its own independent styling and label
Working with SWColor
SWPolygon uses SWColor for fill and stroke in HSB mode:
- The fill color's alpha channel controls transparency independently of the stroke
- Colors are stored by reference (not copied at construction) — replace the
fillColorproperty to change the fill without recreating the polygon
Comparing SWPolygon and SWRegularPolygon
Both classes draw closed filled polygons, but they differ significantly:
| Feature | SWPolygon | SWRegularPolygon |
|---|---|---|
| Vertex placement | Arbitrary — user-defined SWPoints | Computed — equally spaced on circumradius |
| Shape variety | Any simple or complex polygon | Regular n-gons only (equilateral & equiangular) |
| Vertex dragging | Yes — each vertex can be dragged independently | No — shape is always regular |
| Area formula | Shoelace (Gauss) | Geometric (apothem × perimeter / 2) |
| Convexity | Detected dynamically (isConvex) |
Always convex (by definition) |
| Self-intersection | Detected dynamically (isSimple) |
Never self-intersects |
| Rounded corners | No | Yes (Catmull-Rom mode) |
| Spin animation | Via p5 push/translate/rotate/pop in sketch | Built-in rotate() method |
Source Code
The complete SWPolygon class source is in shapeClasses/swPolygon.js.
(click to expand / collapse source)
/*
File: swPolygon.js
Date: 2026-04-22
Author: klp
Workspace: SketchWaveTNT2026-04-21-Stg8
Purpose: SWPolygon class for SketchWaveJS
Represents a closed polygon defined by an ordered array of SWPoint vertices.
Vertices are stored by reference — external mutations are reflected immediately
in subsequent draw calls.
Computed getters (recalculated on every access):
perimeter — sum of all edge lengths (including closing edge)
area — shoelace (Gauss) formula; absolute value; valid for simple polygons
vertexCount — vertices.length
isConvex — cross-product sign consistency test
isSimple — pairwise segment-intersection test for non-adjacent edges
Centroid:
Stored as a drawable SWPoint at the arithmetic mean of vertex positions.
Refreshed by _refreshCentroid() (called inside manipulation methods).
Call updateCentroid(grid) after externally dragging vertices.
Scaling:
scaleAboutCentroid(factor) — scales from originalCentroid using originalVertices snapshot.
rebuildOriginalsAtScale(scale) — re-anchors snapshot after a drag.
Dependencies: p5.js, SWColor, SWPoint, SWGrid.
*/