Overview
SWTearDrop is a SketchWave shape class that draws a smooth parametric teardrop (water-drop) curve on a p5.js canvas. The shape is defined by two parametric equations with a single curve-power parameter that controls tip sharpness:
y(t) = −a · cos(t) t ∈ [0, 2π]
The tip sits at the top at (0, −a) when t = 0. The bottom of the bulge is at (0, +a) when t = π. The widest point of the shape occurs near t ≈ 109°, about one-third of the way down from the bounding-box centre.
sin0(t/2) = 1 everywhere → plain ellipse
Classic teardrop — smooth pointed tip
Elongated drop with sharper tip
Needle-like tip — bulge becomes narrower
Features
- Fill & stroke — independent SWColor objects; pass
nullto suppress either - Curve power —
mparameter shapes tip sharpness continuously from 0 (ellipse) upward - Smoothness —
numPointscontrols vertex count; more vertices = smoother outline - Breathing —
breathe(sinusoid, t)scales bothaandbproportionally via SWSinusoid, preserving the shape’s aspect ratio - Rotation (centre pivot) — the whole shape spins in place; the tip traces a circle around the bounding-box centre
- Rotation (tip pivot) — the tip stays fixed; the bulge swings around it like a pendulum
- Drag —
setCenter(cx, cy)lets you move the shape to any canvas position - Grid support —
drawOnGrid(grid)maps coordinates through a SWGrid for user-space drawings
swColor.js, and optionally swSinusoid.js
(for breathe()) and swGrid.js (for drawOnGrid()).
Constructor
let tear = new SWTearDrop(cx, cy, a, b, fillColor, options);
Required Parameters
| Parameter | Type | Description |
|---|---|---|
cx |
number | Centre x — pixels when using draw(), user units when using drawOnGrid(). The centre is the midpoint between the tip and the bottom of the bulge. |
cy |
number | Centre y — same coordinate system as cx. |
a |
number (≥ 1) | Half-height: distance from the centre to the tip (top) and to the bottom of the bulge. The full height of the bounding box is 2a. |
b |
number (≥ 1) | Width parameter: maximum half-width ≈ 0.77 × b (for m = 1). Scales the horizontal extent of the bulge. |
fillColor |
SWColor | null | Fill colour of the teardrop. Pass null for no fill (outline only). |
Options Object
Pass any combination of these as a plain JS object in the sixth argument:
| Key | Type | Default | Description |
|---|---|---|---|
strokeColor |
SWColor | null | null | Outline colour. Omit or pass null for no stroke. |
strokeWeight |
number | 2 | Stroke width in pixels. Ignored when strokeColor is null. |
m |
number (≥ 0) | 1.0 | Curve-power exponent. 0 = ellipse, 1 = classic teardrop, 2+ = sharper tip. |
numPoints |
number (≥ 3) | 120 | Number of polygon vertices for the outline. Higher values give a smoother curve. 3 gives a triangle. |
rotOrigin |
'center' | 'tip' | 'center' | Rotation pivot. 'center': shape spins in place. 'tip': tip stays fixed, bulge swings around it. |
Constructor Example
let tear;
function setup() {
createCanvas(640, 480);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
const fillC = new SWColor(210, 80, 100, 100, "tearFill");
const strokeC = new SWColor(220, 90, 55, 100, "tearStroke");
tear = new SWTearDrop(width / 2, height / 2, 120, 80, fillC, {
strokeColor: strokeC,
strokeWeight: 2,
m: 3.0,
numPoints: 120,
rotOrigin: 'center'
});
}
function draw() {
background(200, 20, 95);
tear.draw();
}
Key Properties
After construction, these properties are readable. Use setter methods to modify them safely so internal state stays consistent.
cx / cy
Type: number — Centre coordinates in the same units as the constructor. Use setCenter(cx, cy) to move the shape.
a / originalA
Type: number — a is the current half-height baseline. originalA is the value breathing animates around. Use setA(a) to update both at once.
b / originalB
Type: number — b is the current width-parameter baseline. originalB is the breathing baseline. Use setB(b) to update both at once.
_currentA / _currentB
Type: number — Live animated values set by breathe(). Read these (not a or b) if you need the current on-screen size during animation. Reset to originalA/originalB by reset().
m
Type: number (≥ 0) — Curve-power exponent. Change via setM(m). Takes effect immediately on the next draw() call; no rebuild needed.
numPoints
Type: number (≥ 3) — Polygon vertex count. Change via setNumPoints(n). Lower values produce a faceted or angular appearance; 3 gives a triangle.
rotOrigin
Type: string — 'center' or 'tip'. Change via setRotOrigin(o). Switching pivot while the shape is rotating causes a visual jump in position (the rotation angle is preserved, but the pivot point moves). To avoid this, call reset() before switching.
_rotAngle
Type: number (degrees) — Current rotation offset, updated by rotate(). Reset to 0 by reset(). You can directly set this to carry over a rotation angle when rebuilding the shape.
fillColor / strokeColor
Type: SWColor | null — Current colour objects. Change via setFillColor(c) and setStrokeColor(c); pass null to suppress the layer.
Methods
Drawing Methods
draw()
Renders the teardrop in screen (pixel) coordinates using the current animated values (_currentA, _currentB, _rotAngle). Call once per draw() loop frame.
function draw() {
background(200, 20, 95);
tear.draw();
}
drawOnGrid(grid)
Parameters: grid (SWGrid)
Maps cx, cy, and the current a/b values from user coordinates to screen pixels using the provided SWGrid, then renders normally. The rotation angle is applied in screen space.
// tear.cx / tear.cy are in user (math) units
tear.drawOnGrid(myGrid);
Animation Methods
breathe(sinusoid, t)
Parameters: sinusoid (SWSinusoid), t (number — elapsed seconds)
Scales both _currentA and _currentB by sinusoid.getValue(t). Both dimensions scale by the same factor, preserving the shape’s aspect ratio. Build the sinusoid with new SWSinusoid(amp, freq, mid, phase) where phase -Math.PI/2 starts at scale 1.0 and rises first.
let breatheSin;
function setup() {
// ...
// Breathe between 75% and 125% of original size, 3-second period
const amp = (1.25 - 0.75) / 2; // 0.25
const freq = (2 * Math.PI) / 3.0; // 3-second period
const mid = (1.25 + 0.75) / 2; // 1.0 (no net scale change)
breatheSin = new SWSinusoid(amp, freq, mid, -Math.PI / 2);
}
function draw() {
const t = millis() / 1000;
tear.breathe(breatheSin, t);
tear.draw();
}
rotate(degPerSec, t)
Parameters: degPerSec (number), t (number — elapsed seconds)
Sets _rotAngle = degPerSec × t. Positive values rotate clockwise in screen coordinates. The pivot is determined by rotOrigin: 'center' spins the shape in place; 'tip' keeps the tip fixed while the bulge swings around it.
// Spin at 45°/sec clockwise
tear.rotate(45, totalRotTime);
// Spin counter-clockwise
tear.rotate(-90, totalRotTime);
transform(options)
Combines breathing and rotation in a single call. Mirrors the transform() API on SWSun, SWLine, and SWArrow.
| Option | Type | Default | Effect |
|---|---|---|---|
sinusoid | SWSinusoid | null | Breathing (null = skip) |
t | number | 0 | Time in seconds |
degPerSec | number | null | Rotation rate (null = skip) |
// Both animations in one call
tear.transform({
sinusoid: breatheSin,
t: totalTime,
degPerSec: 45
});
reset()
Resets all animation state to initial values: _rotAngle returns to 0; _currentA and _currentB return to originalA/originalB. Does not reset property changes made via setters (colours, m, numPoints, etc.).
tear.reset(); // stop animation effects and return to baseline
Setter Methods
| Method | Parameter | Notes |
|---|---|---|
setCenter(cx, cy) |
two numbers | Move the shape’s centre. Safe to call every frame for mouse drag. |
setA(a) |
number | Updates a, originalA, and _currentA together. Minimum enforced at 1. |
setB(b) |
number | Updates b, originalB, and _currentB together. Minimum enforced at 1. |
setM(m) |
number | Curve-power exponent. Minimum enforced at 0 (0 = ellipse). |
setNumPoints(n) |
number | Vertex count. Floored to integer; minimum enforced at 3. |
setFillColor(c) |
SWColor | null | Pass null to remove fill. |
setStrokeColor(c) |
SWColor | null | Pass null to remove stroke. |
setStrokeWeight(w) |
number | Minimum enforced at 0. |
setRotOrigin(o) |
string | 'center' or 'tip'. Consider calling reset() before switching to avoid a visual jump. |
Utility Methods
toString()
Returns: string — human-readable summary of the teardrop’s current state.
console.log(tear.toString());
// → "SWTearDrop(cx:320.0, cy:240.0, a:120.0, b:80.0, m:3)"
Rotation Pivot: Centre vs. Tip
'center' (default)
The pivot is the shape’s bounding-box centre — the midpoint between the tip and the bottom of the bulge. The teardrop spins in place. The tip traces a full circle around the centre point. This feels like a drop of water spinning on a surface.
Internally: translate(cx, cy), then parametric vertices are drawn as vy = −a·cos(t)
'tip'
The pivot is the tip point itself at (cx, cy − a). The tip stays fixed while the bulge swings around it like a pendulum — or like a tear hanging from a point. The translation shifts to the tip; vertices are offset by +a so the tip is at the local origin.
Internally: translate(cx, cy−a), then vy = a·(1 − cos(t))
Usage Examples
Example 1: Simple Static Teardrop
let tear;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
const fill = new SWColor(210, 80, 100, 100, "fill");
const stroke = new SWColor(220, 90, 55, 100, "stroke");
tear = new SWTearDrop(200, 200, 120, 80, fill, {
strokeColor: stroke,
strokeWeight: 2,
m: 3.0
});
}
function draw() {
background(200, 20, 95);
tear.draw();
}
Example 2: Breathing Teardrop
let tear, breatheSin;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
const fill = new SWColor(210, 80, 100, 100, "fill");
tear = new SWTearDrop(200, 200, 100, 65, fill, { m: 2.0 });
// Breathe between 80% and 120% of size; 3-second period
const amp = 0.20;
const freq = (2 * Math.PI) / 3.0;
breatheSin = new SWSinusoid(amp, freq, 1.0, -Math.PI / 2);
}
function draw() {
background(200, 15, 95);
tear.breathe(breatheSin, millis() / 1000);
tear.draw();
}
Example 3: Spinning (Centre Pivot)
let tear;
let rotElapsed = 0;
let lastT = 0;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
const fill = new SWColor(200, 75, 100, 100, "fill");
tear = new SWTearDrop(200, 200, 110, 75, fill, {
m: 3.0,
rotOrigin: 'center'
});
}
function draw() {
background(210, 20, 95);
const now = millis() / 1000;
rotElapsed += (now - lastT);
lastT = now;
tear.rotate(45, rotElapsed); // 45°/sec clockwise
tear.draw();
}
Example 4: Pendulum Spin (Tip Pivot)
// The tip stays fixed; the bulge swings like a pendulum
let tear;
let rotElapsed = 0;
let lastT = 0;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
const fill = new SWColor(195, 70, 100, 100, "fill");
// Position the tip near the top of the canvas
tear = new SWTearDrop(200, 80, 120, 80, fill, {
m: 2.0,
rotOrigin: 'tip' // tip at (200, 80 - 120) = (200, -40)
// ... use a lower cy so the tip is on-screen
});
// Better: place the shape so the tip stays on canvas
// tear.cy - tear.a = tipY
tear = new SWTearDrop(200, 160, 120, 80, fill, {
m: 2.0,
rotOrigin: 'tip' // tip at (200, 160 - 120) = (200, 40) — near top
});
}
function draw() {
background(210, 20, 95);
const now = millis() / 1000;
rotElapsed += (now - lastT);
lastT = now;
tear.rotate(30, rotElapsed); // 30°/sec clockwise around the tip
tear.draw();
}
Example 5: Breathing + Spinning Together
let tear, breatheSin;
let rotElapsed = 0, lastT = 0;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
const fill = new SWColor(210, 80, 100, 100, "fill");
const stroke = new SWColor(220, 90, 55, 100, "stroke");
tear = new SWTearDrop(200, 200, 100, 68, fill, {
strokeColor: stroke,
strokeWeight: 2,
m: 3.0
});
breatheSin = new SWSinusoid(0.20, (2*Math.PI)/3, 1.0, -Math.PI/2);
}
function draw() {
background(205, 20, 94);
const now = millis() / 1000;
rotElapsed += (now - lastT);
lastT = now;
// Use transform() to apply both in one call
tear.transform({
sinusoid: breatheSin,
t: millis() / 1000,
degPerSec: 45
});
tear.draw();
}
Example 6: Teardrop on a Grid
// Place the teardrop in user (math) coordinates via SWGrid
let myGrid, tear;
function setup() {
createCanvas(480, 480);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
myGrid = new SWGrid(/* options for a centred grid */);
const fill = new SWColor(210, 80, 100, 100, "fill");
// cx / cy / a / b are all in grid (user) units here
tear = new SWTearDrop(0, 0, 3, 2, fill, { m: 2.0 });
}
function draw() {
background(210, 15, 96);
myGrid.draw(); // draw axes
tear.drawOnGrid(myGrid);
}
Integration with SketchWave
Loading Order
SWTearDrop only depends on swColor.js. Load swSinusoid.js if you call breathe() and swGrid.js if you call drawOnGrid():
<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.js"></script>
<!-- SketchWave dependencies -->
<script src="../shapeClasses/swSinusoid.js"></script> <!-- needed for breathe() -->
<script src="../shapeClasses/swColor.js"></script> <!-- required -->
<script src="../shapeClasses/swPoint.js"></script>
<script src="../shapeClasses/swGrid.js"></script> <!-- needed for drawOnGrid() -->
<script src="../shapeClasses/swTearDrop.js"></script>
<!-- Your sketch -->
<script src="../sketches/yourSketch.js"></script>
colorMode Requirement
SWTearDrop uses SWColor, which requires p5.js to be in HSB mode:
function setup() {
createCanvas(width, height);
colorMode(HSB, 360, 100, 100, 100); // required
initializeSWColors(); // required
// ...
}
Animation Timer Pattern (Pause/Resume)
SWTearDrop animations take an accumulated elapsed-time parameter. Track it separately from millis() so you can pause and resume cleanly:
let rotationOn = false;
let totalRotTime = 0;
let lastRotSnap = 0; // millis()/1000 when rotation was last active
function draw() {
background(210, 15, 96);
if (rotationOn) {
const now = millis() / 1000;
totalRotTime += (now - lastRotSnap);
lastRotSnap = now;
tear.rotate(45, totalRotTime);
}
tear.draw();
}
function toggleRotation() {
rotationOn = !rotationOn;
if (rotationOn) {
lastRotSnap = millis() / 1000; // reset delta reference on resume
}
}
Breathing Pause/Resume Pattern
let breathingOn = false;
let breathingStartTime = 0; // millis()/1000 when last started
let breathingPausedElapsed = 0; // seconds already accumulated
function toggleBreathe() {
breathingOn = !breathingOn;
if (breathingOn) {
// Resume: start counting from where we left off
breathingStartTime = millis() / 1000;
} else {
// Pause: save elapsed so far
breathingPausedElapsed += (millis() / 1000) - breathingStartTime;
}
}
function draw() {
background(210, 15, 96);
if (breathingOn) {
const elapsed = breathingPausedElapsed + (millis() / 1000 - breathingStartTime);
tear.breathe(breatheSin, elapsed);
}
tear.draw();
}
Carry-Over Rotation When Rebuilding
When you change a property (like m or numPoints) the demo rebuilds the SWTearDrop object. To avoid a visual snap back to 0°, carry the rotation angle over to the new object:
function buildTearDrop() {
myTear = new SWTearDrop(cx, cy, aVal, bVal, fillC, options);
// Preserve rotation across rebuild
myTear._rotAngle = totalRotTime * rotationRate;
}
Source Code
Show / Hide Source Code
/*
File: swTearDrop.js
Date: 2026-05-19
Author: klp
Workspace: SketchWaveTNT2026-05-01-Stg9
Purpose: SWTearDrop class — a parametric teardrop (water-drop) shape based on:
x(t) = b · sin(t) · sin^m(t / 2)
y(t) = –a · cos(t)
where t ∈ [0, 2π].
Parameters:
a — half-height (distance from bounding-box centre to tip, and
to the bottom of the bulge)
b — width scale (max half-width ≈ 0.77 × b for m = 1)
m — curve-power exponent (m ≥ 0)
0 → perfect ellipse
1.0 → classic teardrop
2+ → elongated, more pointed tip
Default orientation: tip at TOP (minimum y), bulge at BOTTOM.
At t = 0: (0, –a) → tip at the top ✓
At t = π: (0, +a) → bottom of bulge ✓
Width is maximum near t ≈ 109° (y ≈ +a/3, one-third down from centre).
Features:
- Configurable fill colour, stroke colour, and stroke weight
- Breathing animation via SWSinusoid (scales both a and b proportionally)
- Rotation about the shape's bounding-box centre OR about its tip:
'center' — the tip traces a circle around the centre point
'tip' — the tip stays fixed; the bulge swings like a pendulum
- draw() — pixel coordinates (screen space)
- drawOnGrid(grid) — user coordinates mapped through a SWGrid
Dependencies: p5.js, swColor.js, swSinusoid.js (for breathe()), swGrid.js (for drawOnGrid())
Notes: assumes p5.js is running in colorMode(HSB, 360, 100, 100, 100)
*/
console.log("[swTearDrop.js] SWTearDrop class loaded.");
class SWTearDrop {
/**
* @param {number} cx - Centre x (bounding-box midpoint, pixels for draw())
* @param {number} cy - Centre y (bounding-box midpoint — midway between tip and bottom)
* @param {number} a - Half-height ≥ 1: distance from centre to tip (and to bottom)
* @param {number} b - Width parameter ≥ 1: max half-width ≈ 0.77 × b
* @param {SWColor} fillColor - Fill colour (null = no fill)
* @param {Object} [options]
* strokeColor {SWColor} Outline colour (null = no stroke) (default null)
* strokeWeight {number} Stroke weight in pixels (default 2)
* m {number} Curve-power exponent ≥ 0 (default 1.0)
* numPoints {number} Vertex count for smoothness (default 120)
* rotOrigin {string} 'center' | 'tip' (default 'center')
*/
constructor(cx, cy, a, b, fillColor, options = {}) {
this.cx = cx;
this.cy = cy;
this.a = max(1, a);
this.b = max(1, b);
this.originalA = this.a;
this.originalB = this.b;
this.fillColor = fillColor ? SWColor.copy(fillColor) : null;
this.strokeColor = options.strokeColor ? SWColor.copy(options.strokeColor) : null;
this.strokeWeight = options.strokeWeight ?? 2;
this.m = max(0, options.m ?? 1.0);
this.numPoints = max(3, options.numPoints ?? 120);
this.rotOrigin = options.rotOrigin ?? 'center'; // 'center' | 'tip'
// Animation state
this._rotAngle = 0; // accumulated rotation in degrees
this._currentA = this.a;
this._currentB = this.b;
}//end constructor
// ─── Drawing ─────────────────────────────────────────────────────────────
/** Draw in screen (pixel) coordinates. */
draw() {
this._drawAtPx(this.cx, this.cy, this._currentA, this._currentB);
}//end draw
/**
* Draw using user coordinates mapped through a SWGrid.
* @param {SWGrid} grid
*/
drawOnGrid(grid) {
const { x: sx, y: sy } = grid.userToScreen(this.cx, this.cy);
const sa = this._currentA * grid.xScale;
const sb = this._currentB * grid.xScale;
this._drawAtPx(sx, sy, sa, sb);
}//end drawOnGrid
/**
* Core rendering: translate to pivot, rotate, then draw the parametric shape.
*
* Parametric equations (relative to bounding-box centre at origin):
* vx(t) = b · sin(t) · sin^m(t / 2)
* vy(t) = –a · cos(t) [tip at (0, –a); bottom at (0, +a)]
*
* When rotOrigin === 'tip', the pivot is the tip (cx, cy – a):
* We translate to the tip, rotate, then offset vy by +a so the tip
* sits at the pivot origin: vy_rel = –a·cos(t) + a = a·(1 – cos(t))
*
* @param {number} cx Centre x in screen pixels
* @param {number} cy Centre y in screen pixels
* @param {number} a Current half-height in screen pixels
* @param {number} b Current width parameter in screen pixels
*/
_drawAtPx(cx, cy, a, b) {
push();
// ── Appearance ────────────────────────────────────────────────────
if (this.fillColor) {
fill(this.fillColor.col);
} else {
noFill();
}
if (this.strokeColor) {
stroke(this.strokeColor.col);
strokeWeight(this.strokeWeight);
strokeJoin(ROUND);
strokeCap(ROUND);
} else {
noStroke();
}
// ── Transform: translate to pivot, then rotate ────────────────────
// pivotOffY = shift from bounding-box centre to rotation pivot
// 'center' pivot: offset = 0 (pivot at centre)
// 'tip' pivot: offset = –a (pivot at top of teardrop)
const pivotOffY = (this.rotOrigin === 'tip') ? -a : 0;
translate(cx, cy + pivotOffY);
rotate(radians(this._rotAngle));
// ── Parametric vertices (relative to pivot) ───────────────────────
// vx(t) = b · sin(t) · sin^m(t / 2)
// vy(t) = –a · cos(t) – pivotOffY
// For 'center': vy = –a·cos(t) (tip at y = –a above pivot)
// For 'tip': vy = –a·cos(t) + a = a·(1 – cos(t)) (tip at y = 0)
beginShape();
for (let i = 0; i <= this.numPoints; i++) {
const t = (i / this.numPoints) * TWO_PI;
const vx = b * sin(t) * pow(sin(t / 2), this.m);
const vy = -a * cos(t) - pivotOffY;
vertex(vx, vy);
}
endShape(CLOSE);
pop();
}//end _drawAtPx
// ─── Animation ────────────────────────────────────────────────────────────
/**
* Scales the teardrop proportionally via a SWSinusoid.
* Both a and b scale together, preserving the shape's aspect ratio.
* @param {SWSinusoid} sinusoid
* @param {number} t - Elapsed time in seconds
*/
breathe(sinusoid, t) {
const scale = sinusoid.getValue(t);
this._currentA = this.originalA * scale;
this._currentB = this.originalB * scale;
}//end breathe
/**
* Rotates the teardrop. Accumulates continuously as t increases.
* Positive values rotate clockwise in screen coordinates.
* Pivot is set by this.rotOrigin ('center' or 'tip').
* @param {number} degPerSec - Rotation speed (degrees per second)
* @param {number} t - Accumulated elapsed time in seconds
*/
rotate(degPerSec, t) {
this._rotAngle = degPerSec * t;
}//end rotate
/**
* Combines breathing and rotation in one call.
* Mirrors the transform() API on SWSun, SWLine, and SWArrow.
* @param {Object} [options]
* @param {SWSinusoid} [options.sinusoid=null] - Breathing sinusoid (null = no breathe)
* @param {number} [options.t=0] - Time in seconds
* @param {number} [options.degPerSec=null] - Rotation rate in deg/sec (null = no rotate)
*/
transform({ sinusoid = null, t = 0, degPerSec = null } = {}) {
if (sinusoid !== null) this.breathe(sinusoid, t);
if (degPerSec !== null) this.rotate(degPerSec, t);
}//end transform
/**
* Resets all animation state to initial (factory) values.
*/
reset() {
this._rotAngle = 0;
this._currentA = this.originalA;
this._currentB = this.originalB;
}//end reset
// ─── Setters ──────────────────────────────────────────────────────────────
/** Move the shape's centre point. */
setCenter(cx, cy) {
this.cx = cx;
this.cy = cy;
}//end setCenter
/** Set the half-height and reset the breathing baseline. */
setA(a) {
this.a = max(1, a);
this.originalA = this.a;
this._currentA = this.a;
}//end setA
/** Set the width parameter and reset the breathing baseline. */
setB(b) {
this.b = max(1, b);
this.originalB = this.b;
this._currentB = this.b;
}//end setB
/** Set the curve-power exponent (≥ 0; 0 = perfect ellipse). */
setM(m) { this.m = max(0, m); }//end setM
/** Set the number of vertices for the shape outline (≥ 3). */
setNumPoints(n) { this.numPoints = max(3, floor(n)); }//end setNumPoints
/** Set the fill colour (pass null for no fill). */
setFillColor(c) { this.fillColor = c ? SWColor.copy(c) : null; }//end setFillColor
/** Set the stroke colour (pass null for no stroke). */
setStrokeColor(c) { this.strokeColor = c ? SWColor.copy(c) : null; }//end setStrokeColor
/** Set the stroke weight in pixels. */
setStrokeWeight(w) { this.strokeWeight = max(0, w); }//end setStrokeWeight
/** Set the rotation pivot: 'center' or 'tip'. */
setRotOrigin(o) { this.rotOrigin = o; }//end setRotOrigin
/** @returns {string} */
toString() {
return `SWTearDrop(cx:${this.cx.toFixed(1)}, cy:${this.cy.toFixed(1)}, ` +
`a:${this._currentA.toFixed(1)}, b:${this._currentB.toFixed(1)}, m:${this.m})`;
}//end toString
}//end class SWTearDrop