📐 SWTriangle Class Reference
A comprehensive guide to the SketchWaveJS Triangle class
📐 Overview
The SWTriangle class is part of the SketchWaveJS framework, designed for creating interactive, animated triangles in p5.js. A triangle consists of three SWPoint vertices, has a computed centroid, supports custom fill and stroke colors, and includes built-in geometric calculations for area, perimeter, side lengths, angles, and triangle classifications.
- Three
SWPointvertices with automatic centroid calculation - Built-in geometric properties: area, perimeter, side lengths, angles
- Automatic triangle classification: equilateral, isosceles, scalene, acute, right, obtuse, 30-60-90
- Customizable fill and stroke colors using
SWColor - Optional vertex and centroid visualization
- Dual coordinate system support (screen and grid coordinates)
- Animation methods: breathing, rotation, and combined transformations
- Compatible with
SWGridfor Desmos-style coordinate mapping
Dependencies
SWPoint- For vertices and centroidSWColor- For fill and stroke colorsSWGrid- For grid-based coordinate mappingSWSinusoid- For animation effectsp5.js- Graphics library (v1.6.0+)
🔨 Constructor
new SWTriangle(vA, vB, vC, fillColor, options)
Creates a new triangle with three vertex points.
Parameters
vA(SWPoint) - First vertex (typically labeled A)vB(SWPoint) - Second vertex (typically labeled B)vC(SWPoint) - Third vertex (typically labeled C)fillColor(SWColor) - Fill color for the triangle interioroptions(Object, optional) - Configuration object:strokeColor(SWColor) - Border color (optional, no border if undefined)strokeWeight(number) - Border thickness in pixels (default: 2)showVertices(boolean) - Display vertex points (default: true)showCentroid(boolean) - Display centroid point (default: false)
Examples
// Simple triangle (no border, vertices shown)
let ptA = new SWPoint(-3, 4, undefined, 8, swRed, "A");
let ptB = new SWPoint(3, 4, undefined, 8, swGreen, "B");
let ptC = new SWPoint(0, -2, undefined, 8, swBlue, "C");
let triangleABC = new SWTriangle(ptA, ptB, ptC, swYellow);
// Styled triangle with border
let triangle2 = new SWTriangle(
new SWPoint(-5, 5),
new SWPoint(5, 5),
new SWPoint(0, -5),
swCyan, // fill color
{
strokeColor: swBlue, // border color
strokeWeight: 3, // border thickness
showVertices: true, // show vertices
showCentroid: true // show centroid
}
);
// Triangle without visible vertices
let triangle3 = new SWTriangle(
new SWPoint(-2, 0),
new SWPoint(2, 0),
new SWPoint(0, 3),
swMagenta,
{ showVertices: false }
);
📊 Properties
Basic Properties
vA, vB, vC (SWPoint)
The three vertex points of the triangle. These are mutable and can be repositioned to reshape the triangle.
// Access vertices
console.log(triangle.vA.x, triangle.vA.y);
console.log(triangle.vB.x, triangle.vB.y);
console.log(triangle.vC.x, triangle.vC.y);
// Move a vertex (reshapes triangle)
triangle.vA.x = 5;
triangle.vA.y = 10;
centroid (SWPoint, read-only)
The computed center point of the triangle (average of the three vertices). This is automatically calculated by computeCentroid().
// Access centroid
console.log(`Centroid: (${triangle.centroid.x}, ${triangle.centroid.y})`);
// Centroid is automatically updated if you call computeCentroid()
triangle.centroid = triangle.computeCentroid();
fillColor (SWColor)
The fill color for the triangle's interior.
triangle.fillColor = swRed;
triangle.fillColor = new SWColor(180, 70, 80, 100, "customCyan");
strokeColor (SWColor or undefined)
The border color for the triangle. If undefined, no border is drawn.
triangle.strokeColor = swBlue;
triangle.strokeColor = undefined; // No border
strokeWeight (number)
The thickness of the border in pixels (default: 2).
triangle.strokeWeight = 5;
showVertices, showCentroid (boolean)
Control whether to display vertex points and/or the centroid point when drawing.
triangle.showVertices = false; // Hide vertices
triangle.showCentroid = true; // Show centroid
triangle.setShowVertices(true); // Using setter method
triangle.setShowCentroid(false);
Geometric Properties (Getters)
These properties are computed on-the-fly and cannot be directly assigned.
sideAB, sideBC, sideCA (number, getter)
The lengths of the three sides of the triangle. These use SWPoint.distanceTo() internally.
sideAB- Distance from vertex A to vertex BsideBC- Distance from vertex B to vertex CsideCA- Distance from vertex C to vertex A
console.log(`Side AB: ${triangle.sideAB.toFixed(2)}`);
console.log(`Side BC: ${triangle.sideBC.toFixed(2)}`);
console.log(`Side CA: ${triangle.sideCA.toFixed(2)}`);
perimeter (number, getter)
The total perimeter of the triangle (sum of all three side lengths).
console.log(`Perimeter: ${triangle.perimeter.toFixed(2)}`);
area (number, getter)
The area of the triangle, computed using the cross product formula (shoelace method):
Area = |x₁(y₂ - y₃) + x₂(y₃ - y₁) + x₃(y₁ - y₂)| / 2
console.log(`Area: ${triangle.area.toFixed(2)} square units`);
angleA, angleB, angleC (number, getter)
The interior angles of the triangle in degrees, computed using the Law of Cosines:
angleA- Angle at vertex A (between sides AB and CA)angleB- Angle at vertex B (between sides AB and BC)angleC- Angle at vertex C (between sides BC and CA)
Formula: cos(A) = (b² + c² - a²) / (2bc)
console.log(`Angle A: ${triangle.angleA.toFixed(1)}°`);
console.log(`Angle B: ${triangle.angleB.toFixed(1)}°`);
console.log(`Angle C: ${triangle.angleC.toFixed(1)}°`);
// Verify: sum should be 180°
let sum = triangle.angleA + triangle.angleB + triangle.angleC;
console.log(`Sum of angles: ${sum.toFixed(1)}°`);
Classification Properties (Getters)
These boolean getters automatically determine the type of triangle based on its sides and angles.
Side-Based Classifications
isEquilateral (boolean, getter)
Returns true if all three sides are approximately equal (within 1% relative tolerance).
if (triangle.isEquilateral) {
console.log("This is an equilateral triangle!");
}
isIsosceles (boolean, getter)
Returns true if at least two sides are approximately equal (within 1% relative tolerance). Note: Equilateral triangles are also isosceles.
if (triangle.isIsosceles && !triangle.isEquilateral) {
console.log("This is an isosceles (but not equilateral) triangle!");
}
A scalene triangle has all sides of different lengths. You can check for this by verifying that the triangle is not isosceles (and therefore not equilateral).
let isScalene = !triangle.isIsosceles;
if (isScalene) {
console.log("This is a scalene triangle!");
}
Angle-Based Classifications
isAcuteTriangle (boolean, getter)
Returns true if all three angles are less than 90°.
if (triangle.isAcuteTriangle) {
console.log("All angles are acute (< 90°)");
}
isRightTriangle (boolean, getter)
Returns true if one of the angles is approximately 90° (within 0.5° tolerance).
if (triangle.isRightTriangle) {
console.log("This triangle has a right angle!");
}
isObtuseTriangle (boolean, getter)
Returns true if one of the angles is greater than 90°.
if (triangle.isObtuseTriangle) {
console.log("This triangle has an obtuse angle!");
}
is30_60_90 (boolean, getter)
Returns true if the triangle is a special 30-60-90 right triangle (within 0.5° tolerance for each angle).
if (triangle.is30_60_90) {
console.log("This is a 30-60-90 special right triangle!");
}
- A triangle is classified by both sides (scalene/isosceles/equilateral) AND angles (acute/right/obtuse)
- Example: A scalene triangle can be acute, right, or obtuse
- An equilateral triangle is always acute (all angles are 60°)
- The sum of all angles in a triangle always equals 180°
⚙️ Methods
Drawing Methods
draw()
Draws the triangle in screen coordinates using the current fill and stroke colors.
Example
function setup() {
createCanvas(600, 600);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
}
function draw() {
background(220);
triangle.draw(); // Draws in screen coordinates
}
drawOnGrid(grid)
Draws the triangle in grid (user) coordinates, mapping vertices to screen coordinates using the specified SWGrid.
Parameters
grid(SWGrid) - The grid for coordinate mapping
Example
function setup() {
createCanvas(600, 600);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
grid = new SWGrid({
UL: new SWPoint(-10, 10),
LR: new SWPoint(10, -10)
});
}
function draw() {
background(220);
grid.draw();
triangle.drawOnGrid(grid); // Draws in grid coordinates
}
computeCentroid(strokeWeight, strokeColor)
Computes and returns the centroid of the triangle as a new SWPoint. The centroid is the average of the three vertices.
Parameters
strokeWeight(number, optional) - Stroke weight for centroid point (default: 4)strokeColor(SWColor, optional) - Stroke color for centroid point (default: swBlack)
Returns
SWPoint - The centroid point
Example
// Recalculate centroid after moving vertices
triangle.vA.x = 10;
triangle.centroid = triangle.computeCentroid();
Animation Methods
breathe(sinusoid, t)
Makes the triangle 'breathe' by moving each vertex radially from the centroid. The distance is modulated by the provided sinusoid, creating a pulsing effect.
Parameters
sinusoid(SWSinusoid) - Controls the breathing amplitude and frequencyt(number) - Time parameter in seconds
Example
// SWSinusoid(amplitude, frequency, verticalShift, phaseShift)
// Create sinusoid that scales triangle from 0.5× to 1.5× over 3 seconds
const minScale = 0.5;
const maxScale = 1.5;
const period = 3.0; // seconds
const amp = (maxScale - minScale) / 2; // 0.5
const freq = (2 * Math.PI) / period; // ~2.09
const mid = (minScale + maxScale) / 2; // 1.0
let breathe = new SWSinusoid(amp, freq, mid, 0);
function draw() {
background(220);
grid.draw();
// Apply breathing animation
triangle.breathe(breathe, frameCount * 0.02);
triangle.drawOnGrid(grid);
}
rotateAboutCentroid(degPerSec, t)
Rotates the triangle about its centroid. The rotation is applied to the original vertex positions.
Parameters
degPerSec(number) - Angular velocity in degrees per second (counterclockwise is positive)t(number) - Time parameter in seconds
Example
let triangle;
function setup() {
createCanvas(600, 600);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
let ptA = new SWPoint(-3, 4, undefined, 8, swRed, "A");
let ptB = new SWPoint(3, 4, undefined, 8, swGreen, "B");
let ptC = new SWPoint(0, -2, undefined, 8, swBlue, "C");
triangle = new SWTriangle(ptA, ptB, ptC, swYellow, {strokeColor: swOrange, strokeWeight: 2});
}
function draw() {
background(220);
grid.draw();
// Rotate at 45 degrees per second
triangle.rotateAboutCentroid(45, frameCount / frameRate());
triangle.drawOnGrid(grid);
}
rotateAboutCentroidBy(angleDeg)
Rotates the triangle about its centroid by a specific angle (not time-based).
Parameters
angleDeg(number) - Angle in degrees (counterclockwise is positive)
Example
// Rotate by 30 degrees
triangle.rotateAboutCentroidBy(30);
transform({sinusoid, t, degPerSec})
Applies breathing (scaling), rotation, or both to the triangle simultaneously. This method combines effects without interference, always starting from the original vertices.
Parameters (Object)
sinusoid(SWSinusoid, optional) - Controls breathing/scaling effectt(number) - Time parameter in secondsdegPerSec(number, optional) - Angular velocity for rotation
Examples
// Example 1: Breathing only
const minScale = 0.7, maxScale = 1.3, period = 4.0;
const amp = (maxScale - minScale) / 2;
const freq = (2 * Math.PI) / period;
const mid = (minScale + maxScale) / 2;
let breathe = new SWSinusoid(amp, freq, mid, 0);
function draw() {
background(220);
grid.draw();
triangle.transform({
sinusoid: breathe,
t: frameCount * 0.02
});
triangle.drawOnGrid(grid);
}
// Example 2: Rotation only
function draw() {
background(220);
grid.draw();
triangle.transform({
degPerSec: 30,
t: frameCount / frameRate()
});
triangle.drawOnGrid(grid);
}
// Example 3: Combined breathing and rotation
function draw() {
background(220);
grid.draw();
triangle.transform({
sinusoid: breathe,
degPerSec: 45,
t: frameCount * 0.02
});
triangle.drawOnGrid(grid);
}
transform() method always applies breathing first, then rotation. Both effects are computed from the original vertex positions, ensuring smooth, predictable animations.
Utility Methods
reset()
Resets the triangle to its original vertex positions (stored when the triangle was created). This is useful after animations to restore the initial state.
Example
// Reset triangle after animation
triangle.reset();
setShowVertices(show)
Sets whether to display vertex points when drawing the triangle.
Parameters
show(boolean) - True to show vertices, false to hide (default: true)
Example
triangle.setShowVertices(false); // Hide vertices
setShowCentroid(show)
Sets whether to display the centroid point when drawing the triangle.
Parameters
show(boolean) - True to show centroid, false to hide (default: false)
Example
triangle.setShowCentroid(true); // Show centroid
horizShiftBy(xInc)
Shifts the entire triangle horizontally by the specified amount. All vertices and the centroid are updated.
Parameters
xInc(number) - Amount to shift in the x direction
Example
// Move triangle 5 units to the right
triangle.horizShiftBy(5);
// Move triangle 3 units to the left
triangle.horizShiftBy(-3);
toString()
Returns a string representation of the triangle with vertex coordinates, centroid, area, and perimeter.
Returns
string - String representation of the triangle
Example
console.log(triangle.toString());
// Output: "SWTriangle(vA: (x, y), vB: (x, y), vC: (x, y),
// centroid: (x, y), area: A, perimeter: P)"
📝 Usage Examples
Example 1: Basic Static Triangle
Create and display a simple triangle with colored vertices.
let grid, triangle;
function setup() {
createCanvas(600, 600);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
let ptA = new SWPoint(-5, -5, undefined, 10, swRed, "A");
let ptB = new SWPoint(5, -5, undefined, 10, swGreen, "B");
let ptC = new SWPoint(0, 5, undefined, 10, swBlue, "C");
triangle = new SWTriangle(ptA, ptB, ptC, swYellow, {
strokeColor: swOrange,
strokeWeight: 3,
showVertices: true,
showCentroid: true
});
noLoop();
}
function draw() {
background(220);
grid.draw();
triangle.drawOnGrid(grid);
// Display geometric properties
fill(0);
textAlign(LEFT, TOP);
textSize(14);
text(`Area: ${triangle.area.toFixed(2)}`, 10, 10);
text(`Perimeter: ${triangle.perimeter.toFixed(2)}`, 10, 30);
text(`Angles: ${triangle.angleA.toFixed(1)}°, ${triangle.angleB.toFixed(1)}°, ${triangle.angleC.toFixed(1)}°`, 10, 50);
}
Example 2: Interactive Draggable Triangle
Enable users to drag vertices or the centroid to reshape the triangle.
let grid, triangle;
let draggedPoint = null;
function setup() {
createCanvas(600, 600);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
let ptA = new SWPoint(-3, 4, undefined, 12, swRed, "A");
let ptB = new SWPoint(3, 4, undefined, 12, swGreen, "B");
let ptC = new SWPoint(0, -3, undefined, 12, swBlue, "C");
ptA.setDraggable(true);
ptB.setDraggable(true);
ptC.setDraggable(true);
triangle = new SWTriangle(ptA, ptB, ptC, swCyan, {
strokeColor: swBlue,
strokeWeight: 2,
showCentroid: true
});
}
function draw() {
background(240);
grid.draw();
triangle.drawOnGrid(grid);
// Display real-time properties
fill(0);
textAlign(LEFT, TOP);
textSize(14);
text(`Area: ${triangle.area.toFixed(2)}`, 10, 10);
text(`Perimeter: ${triangle.perimeter.toFixed(2)}`, 10, 30);
let typeStr = '';
if (triangle.isEquilateral) typeStr = 'Equilateral';
else if (triangle.isIsosceles) typeStr = 'Isosceles';
else typeStr = 'Scalene';
if (triangle.isRightTriangle) typeStr += ', Right';
else if (triangle.isObtuseTriangle) typeStr += ', Obtuse';
else if (triangle.isAcuteTriangle) typeStr += ', Acute';
text(`Type: ${typeStr}`, 10, 50);
}
function mousePressed() {
if (triangle.vA.containsPoint(mouseX, mouseY, grid, 15)) {
draggedPoint = triangle.vA;
} else if (triangle.vB.containsPoint(mouseX, mouseY, grid, 15)) {
draggedPoint = triangle.vB;
} else if (triangle.vC.containsPoint(mouseX, mouseY, grid, 15)) {
draggedPoint = triangle.vC;
}
}
function mouseDragged() {
if (draggedPoint) {
let userCoords = grid.screenToUser(mouseX, mouseY);
draggedPoint.x = userCoords.x;
draggedPoint.y = userCoords.y;
triangle.centroid = triangle.computeCentroid();
redraw();
}
}
function mouseReleased() {
draggedPoint = null;
}
Example 3: Breathing Triangle Animation
let grid, triangle, breatheSinusoid;
function setup() {
createCanvas(600, 600);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
let ptA = new SWPoint(-4, -3, undefined, 8, swRed, "A");
let ptB = new SWPoint(4, -3, undefined, 8, swGreen, "B");
let ptC = new SWPoint(0, 4, undefined, 8, swBlue, "C");
triangle = new SWTriangle(ptA, ptB, ptC, swMagenta, {
strokeColor: swPurple,
strokeWeight: 2
});
// Scale from 0.6× to 1.4× over 3 seconds
const minScale = 0.6, maxScale = 1.4, period = 3.0;
const amp = (maxScale - minScale) / 2;
const freq = (2 * Math.PI) / period;
const mid = (minScale + maxScale) / 2;
breatheSinusoid = new SWSinusoid(amp, freq, mid, 0);
}
function draw() {
background(220);
grid.draw();
triangle.breathe(breatheSinusoid, frameCount * 0.02);
triangle.drawOnGrid(grid);
// Show current area
fill(0);
textAlign(LEFT, TOP);
textSize(14);
text(`Area: ${triangle.area.toFixed(2)}`, 10, 10);
}
Example 4: Rotating Triangle
let grid, triangle;
function setup() {
createCanvas(600, 600);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
// Create right triangle
let ptA = new SWPoint(-3, -2, undefined, 8, swRed, "A");
let ptB = new SWPoint(3, -2, undefined, 8, swGreen, "B");
let ptC = new SWPoint(3, 4, undefined, 8, swBlue, "C");
triangle = new SWTriangle(ptA, ptB, ptC, swCyan, {
strokeColor: swBlue,
strokeWeight: 2,
showCentroid: true
});
}
function draw() {
background(220);
grid.draw();
// Rotate at 60 degrees per second
triangle.rotateAboutCentroid(60, frameCount / frameRate());
triangle.drawOnGrid(grid);
}
Example 5: Combined Breathing and Rotation
let grid, triangle, breatheSinusoid;
function setup() {
createCanvas(600, 600);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
let ptA = new SWPoint(-4, -3, undefined, 8, swRed, "A");
let ptB = new SWPoint(4, -3, undefined, 8, swGreen, "B");
let ptC = new SWPoint(0, 4, undefined, 8, swBlue, "C");
triangle = new SWTriangle(ptA, ptB, ptC, swYellow, {
strokeColor: swOrange,
strokeWeight: 2
});
const minScale = 0.7, maxScale = 1.3, period = 4.0;
const amp = (maxScale - minScale) / 2;
const freq = (2 * Math.PI) / period;
const mid = (minScale + maxScale) / 2;
breatheSinusoid = new SWSinusoid(amp, freq, mid, 0);
}
function draw() {
background(220);
grid.draw();
// Combine breathing and rotation
triangle.transform({
sinusoid: breatheSinusoid,
degPerSec: 45,
t: frameCount * 0.02
});
triangle.drawOnGrid(grid);
}
Example 6: Triangle Classification Display
let grid, triangle;
let draggedPoint = null;
function setup() {
createCanvas(600, 600);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
let ptA = new SWPoint(-5, 0, undefined, 12, swRed, "A");
let ptB = new SWPoint(5, 0, undefined, 12, swGreen, "B");
let ptC = new SWPoint(0, 7, undefined, 12, swBlue, "C");
ptA.setDraggable(true);
ptB.setDraggable(true);
ptC.setDraggable(true);
triangle = new SWTriangle(ptA, ptB, ptC, swCyan, {
strokeColor: swBlue,
strokeWeight: 2
});
}
function draw() {
background(240);
grid.draw();
triangle.drawOnGrid(grid);
// Display comprehensive classification
fill(0);
textAlign(LEFT, TOP);
textSize(14);
let y = 10;
text(`Area: ${triangle.area.toFixed(2)} sq units`, 10, y); y += 20;
text(`Perimeter: ${triangle.perimeter.toFixed(2)} units`, 10, y); y += 20;
text(`Sides: ${triangle.sideAB.toFixed(2)}, ${triangle.sideBC.toFixed(2)}, ${triangle.sideCA.toFixed(2)}`, 10, y); y += 20;
text(`Angles: ${triangle.angleA.toFixed(1)}°, ${triangle.angleB.toFixed(1)}°, ${triangle.angleC.toFixed(1)}°`, 10, y); y += 25;
// Side-based classification
text('Side Classification:', 10, y); y += 20;
if (triangle.isEquilateral) {
text(' ✓ Equilateral', 10, y);
} else if (triangle.isIsosceles) {
text(' ✓ Isosceles', 10, y);
} else {
text(' ✓ Scalene', 10, y);
}
y += 25;
// Angle-based classification
text('Angle Classification:', 10, y); y += 20;
if (triangle.is30_60_90) {
text(' ✓ 30-60-90 Special Right Triangle', 10, y);
} else if (triangle.isRightTriangle) {
text(' ✓ Right Triangle', 10, y);
} else if (triangle.isObtuseTriangle) {
text(' ✓ Obtuse Triangle', 10, y);
} else if (triangle.isAcuteTriangle) {
text(' ✓ Acute Triangle', 10, y);
}
}
function mousePressed() {
if (triangle.vA.containsPoint(mouseX, mouseY, grid, 15)) {
draggedPoint = triangle.vA;
} else if (triangle.vB.containsPoint(mouseX, mouseY, grid, 15)) {
draggedPoint = triangle.vB;
} else if (triangle.vC.containsPoint(mouseX, mouseY, grid, 15)) {
draggedPoint = triangle.vC;
}
}
function mouseDragged() {
if (draggedPoint) {
let userCoords = grid.screenToUser(mouseX, mouseY);
draggedPoint.x = userCoords.x;
draggedPoint.y = userCoords.y;
triangle.centroid = triangle.computeCentroid();
redraw();
}
}
function mouseReleased() {
draggedPoint = null;
}
💡 Best Practices & Tips
Geometric properties (area, perimeter, angles) are computed on-the-fly via getters. For performance-critical applications where you need these values frequently in a single frame, consider storing them in local variables:
// Instead of calling getter multiple times:
if (triangle.area > 50 && triangle.area < 100) { ... }
// Store in a variable:
let area = triangle.area;
if (area > 50 && area < 100) { ... }
If you manually modify vertex positions (e.g., triangle.vA.x = 5), you must update the centroid manually:
triangle.vA.x = 10;
triangle.vA.y = 5;
triangle.centroid = triangle.computeCentroid();
- Always use
transform()for combined effects rather than callingbreathe()androtateAboutCentroid()separately - Store original vertices by calling
reset()before starting new animations - For smooth animations, use consistent time parameters (e.g.,
frameCount / frameRate())
Remember that triangles have two independent classification systems:
- By sides: Scalene, Isosceles, or Equilateral
- By angles: Acute, Right, or Obtuse
A scalene triangle can be acute, right, or obtuse. These are not mutually exclusive!
- Don't forget to call
computeCentroid()after manually moving vertices - Remember that
isIsoscelesreturns true for equilateral triangles (since they have equal sides) - Angle calculations use tolerances (0.5°) to account for floating-point precision
- If using animations, always call
reset()before switching between different animation modes
For best visual results:
- Use contrasting colors for fill and stroke (e.g., light fill with darker border)
- Consider setting
showVertices: falsefor cleaner animations - Use
showCentroid: truewhen demonstrating rotation or breathing effects - Vertex colors are independent of the triangle's fill/stroke colors
- Always use
drawOnGrid(grid)instead ofdraw()when working with coordinate systems - Vertices are stored in user (grid) coordinates, not screen coordinates
- When checking for mouse interaction with vertices, pass the grid to
containsPoint(mouseX, mouseY, grid, tolerance)
// Print all triangle information
console.log(triangle.toString());
// Check angles sum to 180°
let angleSum = triangle.angleA + triangle.angleB + triangle.angleC;
console.log(`Angle sum: ${angleSum.toFixed(2)}° (should be 180°)`);
// Verify Pythagorean theorem for right triangles
if (triangle.isRightTriangle) {
let sides = [triangle.sideAB, triangle.sideBC, triangle.sideCA].sort((a,b) => a-b);
let pyth = sides[0]**2 + sides[1]**2;
let hyp = sides[2]**2;
console.log(`Pythagorean check: ${pyth.toFixed(2)} ≈ ${hyp.toFixed(2)}`);
}
📄 Source Code
The complete source code for the SWTriangle class:
// swTriangle.js
// SWTriangle: A triangle with 3 SWPoint vertices, centroid, fill color,
// optional border, and animation features
// Author: klp + GitHub Copilot
// Date: 2026-01-27
//
// Dependencies: SWPoint, SWColor, SWGrid, SWSinusoid
console.log("[swTriangle.js] SWTriangle class loaded.");
class SWTriangle {
/**
* @param {SWPoint} vA - First vertex (SWPoint)
* @param {SWPoint} vB - Second vertex (SWPoint)
* @param {SWPoint} vC - Third vertex (SWPoint)
* @param {SWColor} fillColor - Fill color (SWColor instance)
* @param {Object} [options] - Optional: {strokeColor, strokeWeight, showVertices, showCentroid}
*/
constructor(vA, vB, vC, fillColor, options = {}) {
this.vA = vA;
this.vB = vB;
this.vC = vC;
this.fillColor = fillColor;
this.strokeColor = options.strokeColor || undefined;
this.strokeWeight = options.strokeWeight || 2;
this.showVertices = options.showVertices !== undefined ? options.showVertices : true;
this.showCentroid = options.showCentroid !== undefined ? options.showCentroid : false;
const centroidStrokeWeight = this.strokeWeight > 0 ? this.strokeWeight : 4;
const centroidColor = (this.strokeColor && this.strokeColor.col) ? this.strokeColor :
(typeof swBlack !== 'undefined' ? swBlack : new SWColor(0,0,0,100,"black"));
this.centroid = this.computeCentroid(centroidStrokeWeight, centroidColor);
this.originalA = new SWPoint(vA.x, vA.y, vA.z, vA.strokeWeight, vA.strokeColor);
this.originalB = new SWPoint(vB.x, vB.y, vB.z, vB.strokeWeight, vB.strokeColor);
this.originalC = new SWPoint(vC.x, vC.y, vC.z, vC.strokeWeight, vC.strokeColor);
this.originalCentroid = new SWPoint(this.centroid.x, this.centroid.y, undefined,
centroidStrokeWeight, centroidColor);
}
computeCentroid(strokeWeight = 4, strokeColor = (typeof swBlack !== 'undefined' ? swBlack :
new SWColor(0,0,0,100,"black"))) {
const x = (this.vA.x + this.vB.x + this.vC.x) / 3;
const y = (this.vA.y + this.vB.y + this.vC.y) / 3;
return new SWPoint(x, y, undefined, strokeWeight, strokeColor);
}
get sideAB() { return this.vA.distanceTo(this.vB); }
get sideBC() { return this.vB.distanceTo(this.vC); }
get sideCA() { return this.vC.distanceTo(this.vA); }
get perimeter() { return this.sideAB + this.sideBC + this.sideCA; }
get area() {
return Math.abs(
this.vA.x * (this.vB.y - this.vC.y) +
this.vB.x * (this.vC.y - this.vA.y) +
this.vC.x * (this.vA.y - this.vB.y)
) / 2;
}
get angleA() {
const a = this.sideBC, b = this.sideCA, c = this.sideAB;
const cosA = (b * b + c * c - a * a) / (2 * b * c);
return Math.acos(Math.max(-1, Math.min(1, cosA))) * 180 / Math.PI;
}
get angleB() {
const a = this.sideBC, b = this.sideCA, c = this.sideAB;
const cosB = (a * a + c * c - b * b) / (2 * a * c);
return Math.acos(Math.max(-1, Math.min(1, cosB))) * 180 / Math.PI;
}
get angleC() {
const a = this.sideBC, b = this.sideCA, c = this.sideAB;
const cosC = (a * a + b * b - c * c) / (2 * a * b);
return Math.acos(Math.max(-1, Math.min(1, cosC))) * 180 / Math.PI;
}
get isRightTriangle() {
const tolerance = 0.5;
return Math.abs(this.angleA - 90) < tolerance ||
Math.abs(this.angleB - 90) < tolerance ||
Math.abs(this.angleC - 90) < tolerance;
}
get isObtuseTriangle() {
return this.angleA > 90 || this.angleB > 90 || this.angleC > 90;
}
get isAcuteTriangle() {
return this.angleA < 90 && this.angleB < 90 && this.angleC < 90;
}
get isEquilateral() {
const tolerance = 0.01;
const avg = (this.sideAB + this.sideBC + this.sideCA) / 3;
return Math.abs(this.sideAB - avg) < tolerance * avg &&
Math.abs(this.sideBC - avg) < tolerance * avg &&
Math.abs(this.sideCA - avg) < tolerance * avg;
}
get isIsosceles() {
const tolerance = 0.01;
const ab = this.sideAB, bc = this.sideBC, ca = this.sideCA;
const maxSide = Math.max(ab, bc, ca);
const tol = tolerance * maxSide;
return Math.abs(ab - bc) < tol ||
Math.abs(bc - ca) < tol ||
Math.abs(ca - ab) < tol;
}
get is30_60_90() {
const tolerance = 0.5;
const angles = [this.angleA, this.angleB, this.angleC].sort((a, b) => a - b);
return Math.abs(angles[0] - 30) < tolerance &&
Math.abs(angles[1] - 60) < tolerance &&
Math.abs(angles[2] - 90) < tolerance;
}
draw() {
if (this.fillColor && this.fillColor.col) fill(this.fillColor.col);
else noFill();
if (this.strokeColor && this.strokeColor.col) {
stroke(this.strokeColor.col);
strokeWeight(this.strokeWeight);
} else noStroke();
triangle(this.vA.x, this.vA.y, this.vB.x, this.vB.y, this.vC.x, this.vC.y);
noStroke();
noFill();
if (this.showVertices) {
this.vA.draw();
this.vB.draw();
this.vC.draw();
}
if (this.showCentroid) this.centroid.draw();
}
drawOnGrid(grid) {
const a = grid.userToScreen(this.vA.x, this.vA.y);
const b = grid.userToScreen(this.vB.x, this.vB.y);
const c = grid.userToScreen(this.vC.x, this.vC.y);
if (this.fillColor && this.fillColor.col) fill(this.fillColor.col);
else noFill();
if (this.strokeColor && this.strokeColor.col) {
stroke(this.strokeColor.col);
strokeWeight(this.strokeWeight);
} else noStroke();
triangle(a.x, a.y, b.x, b.y, c.x, c.y);
noStroke();
noFill();
if (this.showVertices) {
this.vA.drawOnGrid(grid);
this.vB.drawOnGrid(grid);
this.vC.drawOnGrid(grid);
}
if (this.showCentroid) this.centroid.drawOnGrid(grid);
}
rotateAboutCentroidBy(angleDeg) {
const angleRad = angleDeg * Math.PI / 180;
const cx = this.centroid.x, cy = this.centroid.y;
[this.vA, this.vB, this.vC].forEach((v, i) => {
const orig = [this.originalA, this.originalB, this.originalC][i];
const dx = orig.x - cx, dy = orig.y - cy;
v.x = cx + dx * Math.cos(angleRad) - dy * Math.sin(angleRad);
v.y = cy + dx * Math.sin(angleRad) + dy * Math.cos(angleRad);
});
}
rotateAboutCentroid(degPerSec, t) {
this.rotateAboutCentroidBy(degPerSec * t);
}
breathe(sinusoid, t) {
const scaleFactor = sinusoid.evaluate(t);
const cx = this.originalCentroid.x, cy = this.originalCentroid.y;
this.vA.x = cx + (this.originalA.x - cx) * scaleFactor;
this.vA.y = cy + (this.originalA.y - cy) * scaleFactor;
this.vB.x = cx + (this.originalB.x - cx) * scaleFactor;
this.vB.y = cy + (this.originalB.y - cy) * scaleFactor;
this.vC.x = cx + (this.originalC.x - cx) * scaleFactor;
this.vC.y = cy + (this.originalC.y - cy) * scaleFactor;
this.centroid.x = cx;
this.centroid.y = cy;
}
transform({sinusoid = null, t = 0, degPerSec = null} = {}) {
this.reset();
if (sinusoid) this.breathe(sinusoid, t);
if (degPerSec !== null) this.rotateAboutCentroidBy(degPerSec * t);
}
reset() {
this.vA.x = this.originalA.x; this.vA.y = this.originalA.y;
this.vB.x = this.originalB.x; this.vB.y = this.originalB.y;
this.vC.x = this.originalC.x; this.vC.y = this.originalC.y;
this.centroid.x = this.originalCentroid.x;
this.centroid.y = this.originalCentroid.y;
}
setShowVertices(show = true) { this.showVertices = show; }
setShowCentroid(show = true) { this.showCentroid = show; }
toString() {
return `SWTriangle(vA: (${this.vA.x.toFixed(2)}, ${this.vA.y.toFixed(2)}), ` +
`vB: (${this.vB.x.toFixed(2)}, ${this.vB.y.toFixed(2)}), ` +
`vC: (${this.vC.x.toFixed(2)}, ${this.vC.y.toFixed(2)}), ` +
`centroid: (${this.centroid.x.toFixed(2)}, ${this.centroid.y.toFixed(2)}), ` +
`area: ${this.area.toFixed(2)}, perimeter: ${this.perimeter.toFixed(2)})`;
}
horizShiftBy(xInc) {
this.vA.x += xInc; this.vB.x += xInc; this.vC.x += xInc;
this.originalA.x += xInc; this.originalB.x += xInc; this.originalC.x += xInc;
this.centroid.x += xInc;
this.originalCentroid.x += xInc;
}
}
// Export for module use (if needed)
// export default SWTriangle;