Quick Reference
SWCircleSegment is a SketchWave class for representing circular segment shapes in 2D space. A circle segment is the crescent-shaped region bounded by a chord and its subtended arc — unlike a sector, it does not connect to the center of the generating circle. It is defined by a center point, a radius, and a central angle theta. It supports spin animation, radius breathing, theta breathing, and hue cycling — all independently or simultaneously.
- Extends: Nothing (standalone class)
- Dependencies: SWPoint, SWColor, SWSinusoid, SWGrid, p5.js
- Key Features: Custom fill/stroke colors, arc length / chord length / sagitta / area calculations, spin rotation, radius and theta breathing animations, hue cycling, center point display
- Common Uses: Crescent shapes, lens silhouettes, architectural arches, animated "bite taken out of a circle" effects, geometric explorations
- p5.js Arc Mode:
CHORD— the arc is closed by a straight chord, not connected to the center
Overview
The SWCircleSegment class represents a circular segment — the region bounded by a chord and the arc it subtends — drawn on an SWGrid. Think of it as a "bite taken out of a circle": the chord is the straight edge and the arc is the curved edge. Unlike SWSector (pizza slice), there are no lines connecting to the center — just the arc and the chord closing it directly. The segment's position on the circle is controlled by startAngle, and it can be continuously spun via rotate().
PIE mode (two radii extend to the center), while SWCircleSegment draws in CHORD mode (the arc endpoints are joined by a straight chord). The center property stores the center of the generating circle, but it is not a vertex of the drawn shape.
Angle Convention
SWCircleSegment uses math-space (user-space) angles: angles are measured counterclockwise (CCW) from the positive x-axis, and y increases upward — the standard Cartesian convention. Because p5.js uses a y-down screen space, all angles are negated internally when calling arc(). You never need to worry about this conversion; just pass CCW degrees from +x.
// Default: startAngle=0, theta=120 → segment spans from +x CCW through 120°
let seg = new SWCircleSegment(new SWPoint(0,0), 5, 120);
// startAngle=90 → segment starts at +y axis and sweeps CCW into Q2
let seg2 = new SWCircleSegment(new SWPoint(0,0), 5, 120, 90);
Key Capabilities
- Chord-Closed Arc: Uses p5's
CHORDmode — no connection to the circle's center - Flexible Positioning: Center defined by an SWPoint instance (center of the generating circle)
- Full Styling Control: Independent fill and stroke colors using SWColor
- Rich Geometric Properties: Automatic arc length, chord length, sagitta, and segment area calculations
- Spin Animation: Continuous rotation about the center via
rotate() - Breathing Animations: Oscillate radius or theta independently with SWSinusoid
- Hue Cycling: Animate fill color hue externally via SWSinusoid
- Dual Coordinate Systems: Draw in screen pixels or grid coordinates
- Center Point Display: Optional visualization of the circle's center
Typical Workflow
- Create an SWCircleSegment with center point, radius, theta, startAngle, and colors
- Draw the segment each frame using
drawOnGrid() - Call
rotate()before drawing to spin; callbreatheRadius()orbreatheTheta()after drawing to modulate next frame - Use the elapsed-time pattern for pause/resume of each animation
- Call
reset()to restore the original geometry and color
Constructor
new SWCircleSegment(center, radius, theta, startAngle, thickness, fillColor, strokeColor)Creates a new SWCircleSegment instance with the given geometry and styling.
| Parameter | Type | Default | Description |
|---|---|---|---|
center |
SWPoint | required | Center of the generating circle (NOT a vertex of the drawn shape) |
radius |
number | required | Radius in user units (> 0) |
theta |
number | required | Central angle / angular size in degrees; clamped to [0, 360] |
startAngle |
number | 0 | Arc start angle in degrees CCW from +x axis; sets initial orientation |
thickness |
number | 2 | Border (stroke) thickness in pixels |
fillColor |
SWColor | undefined | Interior fill color (no fill if undefined) |
strokeColor |
SWColor | undefined | Border color — applied to both the arc and the chord (no stroke if undefined) |
// Minimal: center, radius, and theta
let seg1 = new SWCircleSegment(new SWPoint(0, 0), 5, 120);
// With explicit startAngle (segment starts at +y axis, sweeps CCW)
let seg2 = new SWCircleSegment(new SWPoint(0, 0), 5, 120, 90);
// With fill color and border (magenta segment)
let fillCol = new SWColor(300, 100, 100, 80, "magenta");
let strokeCol = new SWColor(300, 100, 50, 100, "darkMagenta");
let seg3 = new SWCircleSegment(new SWPoint(0, 0), 5, 180, 0, 2, fillCol, strokeCol);
// Repositioned segment — a thin crescent at top-right
let c = new SWPoint(3, 2);
let seg4 = new SWCircleSegment(c, 4, 60, 45, 3, fillCol, strokeCol);
Properties
center SWPointThe center of the generating circle. Changing this moves the whole segment. Note: this is the geometric center of the circle, not a corner or endpoint of the segment itself.
seg.center.x = 3; seg.center.y = -2;
radius numberThe radius of the generating circle in user units. Changing this directly does not update computed geometry — use setRadius() for that.
seg.setRadius(8); // also updates arcLength, chordLength, sagitta, area
theta numberThe central angle of the segment in degrees, clamped to [0, 360]. Small theta → narrow sliver; theta near 360° → nearly a full circle with a tiny gap at the chord. Use setTheta() to keep geometry in sync.
seg.setTheta(90); // quarter-circle segment
startAngle numberThe static starting angle (degrees CCW from +x) for the arc. This is the orientation before any rotation accumulates. Use setStartAngle() to change it.
seg.setStartAngle(45); // arc begins at 45°
rotation numberAccumulated rotation in degrees (CCW positive). Set to 0 by the constructor; incremented by rotate(). The effective arc start is startAngle + rotation.
console.log(seg.rotation.toFixed(1) + "°");
thickness numberThe stroke (border) thickness in pixels. Applied to both the arc and the chord. Use setStrokeWeight() to update.
seg.setStrokeWeight(4);
fillColor SWColorThe interior fill color as an SWColor instance. Colors are always copied to avoid shared-mutation bugs.
seg.setFillColor(new SWColor(300, 100, 100, 80, "magenta"));
strokeColor SWColorThe border color as an SWColor instance. This color is applied to both the curved arc edge and the straight chord.
seg.setStrokeColor(swBlack);
originalRadius / originalTheta / originalStartAngle / originalRotation / originalFillColor various restore targetsSnapshot values taken at construction. reset() uses all of these to restore the segment to its initial state.
// Read-only; used internally by reset()
shouldShowCenter booleanWhether to draw the center SWPoint marker. Default is true. Useful for interactive demos where users drag the center to reposition the segment.
seg.setShowCenter(false); // hide center dot
arcLength number computedThe arc length of the segment's curved edge: (theta / 360) × 2πr. Updated automatically when radius or theta changes.
console.log(`Arc length: ${seg.arcLength.toFixed(2)}`);
chordLength number computedThe length of the straight chord: 2r × sin(theta/2). This is the length of the segment's straight edge. Updated automatically when radius or theta changes.
console.log(`Chord length: ${seg.chordLength.toFixed(2)}`);
sagitta number computedThe sagitta (height) of the segment — the distance from the midpoint of the chord to the midpoint of the arc: r × (1 − cos(theta/2)). Updated automatically when radius or theta changes.
console.log(`Sagitta: ${seg.sagitta.toFixed(2)}`);
area number computedThe area of the circular segment (not the full sector): r² / 2 × (theta_rad − sin(theta_rad)). This is strictly the area of the crescent-shaped region between chord and arc. Updated automatically when radius or theta changes.
console.log(`Segment area: ${seg.area.toFixed(2)}`);
Methods
Core Drawing Methods
draw()Draws the circle segment in screen (pixel) coordinates using p5.js. Rarely used directly — prefer drawOnGrid().
void
function draw() {
background(220);
seg.draw(); // center.x/y treated as screen pixels
}
drawOnGrid(grid)Draws the circle segment in user (grid) coordinates. Converts the center position and radius through the SWGrid's coordinate mapping. The average of grid.xScale and grid.yScale is used for the radius so the segment stays circular even on non-square grids.
grid(SWGrid) — the coordinate grid
void
function draw() {
background(220);
grid.draw();
seg.drawOnGrid(grid);
}
Rotation Animation
rotate(deltaAngle)Increments the segment's accumulated rotation by deltaAngle degrees (CCW positive, CW negative). Call each frame to spin the segment about its center. Because the shape is a chord-closed arc, spinning it traces a ring-like path.
deltaAngle(number) — degrees to add torotation
// Spin at 45°/second using elapsed time (deltaT)
seg.rotate(spinSpeed * deltaT); // call BEFORE drawOnGrid
// Or use a fixed increment per frame
seg.rotate(1); // 1 degree per frame
Breathing Animations
breatheRadius(sinusoid, t)Modulates the radius using an SWSinusoid. The radius is set to the sinusoid's value at time t; clamped to a minimum of 0.01. Automatically updates chordLength, sagitta, arcLength, and area. Call after drawOnGrid() so the new value takes effect on the next frame.
sinusoid(SWSinusoid) — controls radius oscillationt(number) — elapsed time in seconds
// Radius oscillates between 2 and 8 over 4 seconds
let radSin = new SWSinusoid(5, 3, 1/4, 0); // center=5, amp=3, freq=0.25 Hz
seg.drawOnGrid(grid);
seg.breatheRadius(radSin, elapsedSeconds);
breatheTheta(sinusoid, t)Modulates the central angle (theta) using an SWSinusoid. Call after drawOnGrid(). _updateGeometry() is called automatically to clamp theta and refresh all computed properties. At small theta the segment is a thin sliver; near 360° it nearly encloses the full circle.
sinusoid(SWSinusoid) — controls theta oscillationt(number) — elapsed time in seconds
// Theta oscillates between 20° and 340° over 3 seconds
let thetaSin = new SWSinusoid(180, 160, 1/3, 0); // center=180°, amp=160°, freq=0.33 Hz
seg.drawOnGrid(grid);
seg.breatheTheta(thetaSin, elapsedSeconds);
Color and Styling Methods
setFillColor(swColor)Sets the fill color. A copy is made automatically.
swColor(SWColor) — new fill color
seg.setFillColor(new SWColor(300, 100, 100, 80, "magenta"));
resetFillColor()Restores the fill color to the original stored at construction.
seg.resetFillColor();
setStrokeColor(swColor)Sets the border (stroke) color, applied to both the arc and the chord.
swColor(SWColor) — new stroke color
seg.setStrokeColor(swBlack);
setStrokeWeight(w)Sets the border thickness in pixels.
w(number) — thickness in pixels
seg.setStrokeWeight(4);
setFillAlpha(alpha)Sets the alpha (transparency) of the fill color. Clamped to [0, 100]; updates the underlying p5.js color immediately.
alpha(number) — 0 = fully transparent, 100 = fully opaque
seg.setFillAlpha(60); // 60% opaque fill
setStrokeAlpha(alpha)Sets the alpha of the stroke (border) color. Clamped to [0, 100].
alpha(number) — 0 = fully transparent, 100 = fully opaque
seg.setStrokeAlpha(80);
Geometry Methods
setRadius(r)Sets the radius and automatically updates arcLength, chordLength, sagitta, and area.
r(number) — new radius in user units
seg.setRadius(7);
console.log(`chord: ${seg.chordLength.toFixed(2)}, sagitta: ${seg.sagitta.toFixed(2)}`);
setTheta(degrees)Sets the central angle (theta) and automatically updates all computed geometry. Value is clamped to [0, 360].
degrees(number) — new angular size in degrees
seg.setTheta(180); // half-circle segment — a perfect semicircle
setStartAngle(degrees)Sets the static starting angle (degrees CCW from +x) without affecting accumulated rotation.
degrees(number) — new start angle
seg.setStartAngle(180); // arc begins on the left side
setShowCenter(show)Controls whether the center SWPoint dot is drawn. The center dot is useful for dragging/repositioning in interactive demos.
show(boolean) — true to show, false to hide (default: true)
seg.setShowCenter(false);
Reset Methods
reset()Restores all animated properties — radius, theta, startAngle, rotation, and fillColor — to their originals captured at construction.
seg.reset(); // full restore to factory state
Utility Methods
static copy(other)Returns a deep copy of an SWCircleSegment instance. All geometry and color values are independently duplicated.
other(SWCircleSegment) — the segment to copy
SWCircleSegment — a new independent instance
let copy = SWCircleSegment.copy(seg1);
toString()Returns a string representation of the segment with all its key properties, including chordLength and sagitta.
string
console.log(seg.toString());
// "SWCircleSegment(center: SWPoint(x: 0, y: 0), radius: 5.00, theta: 120.0°, startAngle: 0°, rotation: 0.0°, chordLength: 8.66, sagitta: 2.50, area: 9.06, arcLength: 10.47)"
Usage Examples
Example 1: Basic Segment with Grid
let grid;
let seg;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
let center = new SWPoint(0, 0);
let fillColor = new SWColor(300, 100, 100, 80, "magenta");
let strokeCol = new SWColor(300, 100, 50, 100, "darkMagenta");
seg = new SWCircleSegment(center, 5, 120, 0, 2, fillColor, strokeCol);
}
function draw() {
background(0, 0, 95);
grid.draw();
seg.drawOnGrid(grid);
}
Example 2: Spinning Segment (Elapsed-Time Approach)
let grid, seg;
let prevT = 0;
const SPIN_SPEED = 60; // degrees per second
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
seg = new SWCircleSegment(new SWPoint(0, 0), 5, 120, 0, 2,
new SWColor(300, 100, 100, 80, "magenta"), swBlack);
}
function draw() {
background(0, 0, 95);
grid.draw();
// Compute deltaT in seconds; spin BEFORE drawing
const t = millis() / 1000;
const deltaT = (prevT > 0) ? (t - prevT) : 0;
prevT = t;
seg.rotate(SPIN_SPEED * deltaT); // CCW positive
seg.drawOnGrid(grid);
}
Example 3: Breathing Radius
let grid, seg, radSin;
let breathStart = 0, breathElapsed = 0;
let shouldBreathe = false;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
seg = new SWCircleSegment(new SWPoint(0, 0), 5, 120, 0, 2,
new SWColor(300, 100, 100, 80, "magenta"), swBlack);
// Center=5, amplitude=3 → range [2, 8], period=4 sec → freq=0.25 Hz
radSin = new SWSinusoid(5, 3, 0.25, 0);
}
function draw() {
background(0, 0, 95);
grid.draw();
seg.drawOnGrid(grid);
if (shouldBreathe) {
const t = millis() / 1000;
breathElapsed += (t - breathStart);
breathStart = t;
seg.breatheRadius(radSin, breathElapsed); // call AFTER draw
}
text(`radius: ${seg.radius.toFixed(2)} chord: ${seg.chordLength.toFixed(2)}`, 10, 20);
}
function keyPressed() {
if (key === 'b') {
shouldBreathe = !shouldBreathe;
breathStart = millis() / 1000;
}
if (key === 'r') { seg.reset(); breathElapsed = 0; }
}
Example 4: Breathing Theta (Crescent Pulsing)
let grid, seg, thetaSin;
let thetaStart = 0, thetaElapsed = 0;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
seg = new SWCircleSegment(new SWPoint(0, 0), 5, 180, 0, 2,
new SWColor(300, 100, 100, 80, "magenta"), swBlack);
// Theta oscillates between 20° and 340°, period=3 sec
thetaSin = new SWSinusoid(180, 160, 1/3, 0);
thetaStart = millis() / 1000;
}
function draw() {
background(0, 0, 20); // dark background
grid.draw();
seg.drawOnGrid(grid);
const t = millis() / 1000;
thetaElapsed += (t - thetaStart);
thetaStart = t;
seg.breatheTheta(thetaSin, thetaElapsed);
}
Example 5: Simultaneous Spin + Radius Breathe + Hue Cycle
let grid, seg, radSin, hueSin;
let prevT = 0;
let rStart = 0, rElapsed = 0;
let hStart = 0, hElapsed = 0;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
seg = new SWCircleSegment(new SWPoint(0, 0), 5, 120, 0, 2,
new SWColor(300, 100, 100, 80, "magenta"), swBlack);
radSin = new SWSinusoid(5, 3, 0.25, 0); // radius 2–8
hueSin = new SWSinusoid(180, 180, 1/3, 0); // hue 0–360
}
function draw() {
background(0, 0, 95);
grid.draw();
const t = millis() / 1000;
const deltaT = (prevT > 0) ? (t - prevT) : 0;
prevT = t;
// 1. Apply spin BEFORE draw
seg.rotate(45 * deltaT);
// 2. Apply hue cycling BEFORE draw
hElapsed += deltaT;
seg.fillColor.h = hueSin.getValue(hElapsed);
seg.fillColor.col = color(seg.fillColor.h,
seg.fillColor.s,
seg.fillColor.b,
seg.fillColor.a);
seg.drawOnGrid(grid);
// 3. Apply breathing AFTER draw
rElapsed += deltaT;
seg.breatheRadius(radSin, rElapsed);
}
Example 6: Ring of Segments
let grid;
let segments = [];
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
// 6 segments evenly distributed around the circle
const count = 6;
const theta = 40; // degrees — gap between segments
for (let i = 0; i < count; i++) {
const startAngle = i * (360 / count);
const hue = (i / count) * 360;
const fillColor = new SWColor(hue, 80, 90, 80, `seg${i}`);
const strokeCol = new SWColor(hue, 80, 50, 100, `stroke${i}`);
segments.push(new SWCircleSegment(
new SWPoint(0, 0), 6, theta, startAngle, 2, fillColor, strokeCol
));
}
}
function draw() {
background(0, 0, 95);
grid.draw();
segments.forEach(s => s.drawOnGrid(grid));
}
Best Practices
1. Animation Ordering
- Spin/hue changes BEFORE draw: These affect what is rendered this frame
- Breathing AFTER draw: The new value takes effect next frame, matching SWSector/SWDisk convention
seg.rotate(speed * deltaT); // spin → before draw
seg.drawOnGrid(grid); // draw
seg.breatheRadius(sin, t); // breathe → after draw
2. Elapsed Time vs. frameCount
- Use elapsed seconds (not frame count) for all sinusoid time parameters; this decouples animation speed from frame rate
- Track
startTimeandelapsedseparately per animation so each can be paused and resumed independently
// Pattern for pauseable elapsed-time animation
let breathStart = 0, breathElapsed = 0, isBrething = false;
function toggleBreathe() {
isBrething = !isBrething;
if (isBrething) breathStart = millis() / 1000;
}
// In draw():
if (isBrething) {
const t = millis() / 1000;
breathElapsed += (t - breathStart);
breathStart = t;
seg.breatheRadius(radSin, breathElapsed);
}
3. Angle Convention
- Always pass user-space degrees (CCW from +x) to SWCircleSegment; the class handles the p5.js y-flip internally
startAngle=0means the arc begins at the +x axis and sweeps CCW by theta degrees- Positive
rotate()deltas spin CCW; negative values spin CW
4. Color Management
- Always pass SWColor instances; the constructor copies them to prevent shared-mutation bugs
- For hue cycling, modify
seg.fillColor.hdirectly and rebuild.colbefore drawing - Call
reset()orresetFillColor()to cleanly restore original colors
5. SWSinusoid Setup for Breathing
- The sinusoid's center value is the midpoint of the animation range
- The amplitude is half the desired peak-to-peak swing
- Example: range [2, 8] → center = 5, amplitude = 3
// Radius breathes between minVal and maxVal
const center = (minVal + maxVal) / 2;
const amplitude = (maxVal - minVal) / 2;
const frequency = 1 / period; // Hz (cycles per second)
let radSin = new SWSinusoid(center, amplitude, frequency, 0);
6. Theta Range for Breathe Theta
- Keep the max theta below 360° (e.g., 340°) — at exactly 360° the chord collapses and the shape becomes a full circle with no visible chord
- Keep the min theta above 0° — at 0° the segment disappears entirely
Integration with Other SketchWave Classes
Script Loading Order
Load dependencies before SWCircleSegment:
<!-- 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/swSinusoid.js"></script>
<script src="shapeClasses/swColor.js"></script>
<script src="shapeClasses/swPoint.js"></script>
<script src="shapeClasses/swGrid.js"></script>
<script src="shapeClasses/swCircleSegment.js"></script>
<!-- Your sketch -->
<script src="sketches/yourSketch.js"></script>
Working with SWPoint
SWCircleSegment uses SWPoint for its center:
- The center is a full SWPoint instance — it is drawn as a small dot when
shouldShowCenteris true - Drag or reposition the segment by changing
seg.center.xandseg.center.y - The center is the center of the generating circle, not a vertex of the chord; the segment does not include this point in its filled region
// Move center to user coordinates (2, -3)
seg.center.x = 2;
seg.center.y = -3;
Working with SWColor
SWCircleSegment uses SWColor for all color management (HSB mode):
- Colors are automatically copied to prevent shared-mutation bugs
- For hue cycling, mutate
seg.fillColor.hthen rebuildseg.fillColor.col
// Hue cycling by direct mutation (done before drawOnGrid)
seg.fillColor.h = hueSin.getValue(hElapsed);
seg.fillColor.col = color(seg.fillColor.h,
seg.fillColor.s,
seg.fillColor.b,
seg.fillColor.a);
Working with SWGrid
SWCircleSegment integrates with SWGrid for coordinate mapping:
- Use
drawOnGrid(grid)to draw in user units — the grid converts both position and radius to screen pixels - The average of
grid.xScaleandgrid.yScaleis used for the radius, keeping the segment circular even on non-square grids
Working with SWSinusoid
SWCircleSegment's breathing methods accept SWSinusoid instances:
- One sinusoid for
breatheRadius(), a separate one forbreatheTheta() - The sinusoid's
getValue(t)is called with elapsed time in seconds
Comparing SWCircleSegment and SWSector
These two classes share the same constructor signature and animations but draw fundamentally different shapes:
| Feature | SWSector | SWCircleSegment |
|---|---|---|
| Shape | Pizza slice (two radii + arc) | Crescent (chord + arc only) |
| p5 arc mode | PIE |
CHORD |
| Anchor point | vertex — the tip (included in shape) |
center — the circle center (outside the filled region) |
| Computed area | (theta/360) × πr² (sector area) |
r²/2 × (θ − sin θ) (segment area) |
| Extra properties | arcLength, area |
arcLength, chordLength, sagitta, area |
| Show anchor | setShowVertex() |
setShowCenter() |
Source Code
The complete SWCircleSegment class implementation:
Show/Hide Source Code
/*
File: swCircleSegment.js
Date: 2026-04-21
Author: klp
App: SketchWaveTNT2026-04-21-Stg8
Purpose: SWCircleSegment class for SketchWaveJS
SWCircleSegment represents a circular segment -- the region between a chord
and its subtended arc -- defined by:
- A center SWPoint (center of the generating circle)
- A radius (in user units)
- A theta (angular size / central angle in degrees, clamped 0-360)
- A startAngle (degrees CCW from the +x axis, where the arc begins)
- A rotation offset (degrees, accumulated via rotate())
Unlike SWSector (pizza slice), a segment does NOT connect to the center.
The shape is closed by a chord drawn directly from one arc endpoint to the
other. In p5.js this is the CHORD arc mode.
Default orientation: arc begins along the +x axis and sweeps CCW through
theta degrees. Set startAngle to establish a different static starting
position before rotation begins.
Animations (all independent, all composable):
- rotate(delta): spins the segment about the circle's center.
- breatheRadius(sinusoid, t): oscillates the radius with an SWSinusoid.
- breatheTheta(sinusoid, t): oscillates the angular size with an SWSinusoid.
Color cycling: mutate fillColor.h and rebuild fillColor.col before drawing,
just like SWSector / SWDisk.
Angle convention:
User space (math): angles are CCW from +x axis, y increases upward.
p5 / screen space: angles are CW from +x axis, y increases downward.
Conversion: p5_angle = -user_angle (negate because y is flipped).
Drawing a segment whose arc start is at totalAngle = startAngle + rotation,
spanning theta degrees CCW:
p5 arc start = -radians(totalAngle + theta)
p5 arc stop = -radians(totalAngle)
arc(cx, cy, 2r, 2r, p5Start, p5Stop, CHORD) -- p5 arc is clockwise.
Geometric extras (recomputed by _updateGeometry):
arcLength = (theta/360) * 2*PI * r
chordLength = 2 * r * sin(theta_rad / 2)
sagitta = r * (1 - cos(theta_rad / 2)) (height of the segment)
area = (theta_rad/2 - sin(theta_rad)/2) * r^2 (segment area formula)
Notes:
- Assumes p5.js, SWColor, SWPoint, SWGrid, SWSinusoid are loaded.
- Consistent API with SWSector, SWDisk, SWLine, SWTriangle, etc.
- setFillAlpha / setStrokeAlpha clamp alpha to [0, 100].
*/
console.log("[swCircleSegment.js] SWCircleSegment class loaded.");
class SWCircleSegment {
constructor(center, radius, theta, startAngle = 0, thickness = 2,
fillColor = undefined, strokeColor = undefined) {
this.center = center;
this.radius = radius;
this.theta = theta;
this.startAngle = startAngle;
this.rotation = 0;
this.thickness = thickness;
this.fillColor = fillColor ? SWColor.copy(fillColor) : undefined;
this.strokeColor = strokeColor ? SWColor.copy(strokeColor) : undefined;
this.originalRadius = radius;
this.originalTheta = theta;
this.originalStartAngle = startAngle;
this.originalRotation = 0;
this.originalFillColor = fillColor ? SWColor.copy(fillColor) : undefined;
this.shouldShowCenter = true;
this._updateGeometry();
}
_updateGeometry() {
this.theta = Math.max(0, Math.min(360, this.theta));
const thetaRad = this.theta * Math.PI / 180;
this.arcLength = (this.theta / 360) * 2 * Math.PI * this.radius;
this.chordLength = 2 * this.radius * Math.sin(thetaRad / 2);
this.sagitta = this.radius * (1 - Math.cos(thetaRad / 2));
this.area = 0.5 * this.radius * this.radius * (thetaRad - Math.sin(thetaRad));
}
draw() {
this._drawArc(this.center.x, this.center.y, this.radius);
if (this.shouldShowCenter && this.center && this.center.draw) {
this.center.draw(this.strokeColor);
}
}
drawOnGrid(grid) {
const { x: cx, y: cy } = grid.userToScreen(this.center.x, this.center.y);
const rScreen = (grid.xScale * this.radius + grid.yScale * this.radius) / 2;
this._drawArc(cx, cy, rScreen);
if (this.shouldShowCenter && this.center && this.center.drawOnGrid) {
this.center.drawOnGrid(grid, this.strokeColor);
}
}
_drawArc(cx, cy, r) {
const totalAngle = this.startAngle + this.rotation;
const p5Start = -radians(totalAngle + this.theta);
const p5Stop = -radians(totalAngle);
if (this.fillColor && this.fillColor.col) {
fill(this.fillColor.col);
} else {
noFill();
}
if (this.strokeColor && this.strokeColor.col) {
stroke(this.strokeColor.col);
} else {
noStroke();
}
strokeWeight(this.thickness);
arc(cx, cy, 2 * r, 2 * r, p5Start, p5Stop, CHORD);
noStroke();
noFill();
strokeWeight(1);
}
rotate(deltaAngle) {
this.rotation += deltaAngle;
}
breatheRadius(sinusoid, t) {
this.radius = Math.max(0.01, sinusoid.getValue(t));
this._updateGeometry();
}
breatheTheta(sinusoid, t) {
this.theta = sinusoid.getValue(t);
this._updateGeometry();
}
setFillAlpha(alpha) {
if (this.fillColor) {
this.fillColor.a = Math.max(0, Math.min(100, alpha));
this.fillColor.col = color(
this.fillColor.h, this.fillColor.s,
this.fillColor.b, this.fillColor.a
);
}
}
setStrokeAlpha(alpha) {
if (this.strokeColor) {
this.strokeColor.a = Math.max(0, Math.min(100, alpha));
this.strokeColor.col = color(
this.strokeColor.h, this.strokeColor.s,
this.strokeColor.b, this.strokeColor.a
);
}
}
reset() {
this.radius = this.originalRadius;
this.theta = this.originalTheta;
this.startAngle = this.originalStartAngle;
this.rotation = this.originalRotation;
if (this.originalFillColor) {
this.fillColor = SWColor.copy(this.originalFillColor);
}
this._updateGeometry();
}
resetFillColor() {
if (this.originalFillColor) {
this.fillColor = SWColor.copy(this.originalFillColor);
}
}
setFillColor(swColor) { this.fillColor = swColor ? SWColor.copy(swColor) : undefined; }
setStrokeColor(swColor) { this.strokeColor = swColor ? SWColor.copy(swColor) : undefined; }
setStrokeWeight(w) { this.thickness = w; }
setRadius(r) { this.radius = r; this._updateGeometry(); }
setTheta(degrees) { this.theta = degrees; this._updateGeometry(); }
setStartAngle(degrees) { this.startAngle = degrees; }
setShowCenter(show = true) { this.shouldShowCenter = show; }
static copy(other) {
if (!(other instanceof SWCircleSegment)) {
throw new Error('Argument to SWCircleSegment.copy must be an SWCircleSegment instance');
}
const s = new SWCircleSegment(
SWPoint.copy(other.center),
other.originalRadius,
other.originalTheta,
other.originalStartAngle,
other.thickness,
other.fillColor,
other.strokeColor
);
s.radius = other.radius;
s.theta = other.theta;
s.startAngle = other.startAngle;
s.rotation = other.rotation;
s.shouldShowCenter = other.shouldShowCenter;
s._updateGeometry();
return s;
}
toString() {
return `SWCircleSegment(center: ${this.center.toString()}, ` +
`radius: ${this.radius.toFixed(2)}, theta: ${this.theta.toFixed(1)}°, ` +
`startAngle: ${this.startAngle}°, rotation: ${this.rotation.toFixed(1)}°, ` +
`chordLength: ${this.chordLength.toFixed(2)}, sagitta: ${this.sagitta.toFixed(2)}, ` +
`area: ${this.area.toFixed(2)}, arcLength: ${this.arcLength.toFixed(2)})`;
}
}//end SWCircleSegment class