⚡ Quick Reference
Construct
Draw each frame
Geometry
Colors
Utility
Overview
SWTriDisk is a composite shape made of three collinear
SWDisk objects. The center disk
sits at the declared center point and is drawn last (on top). The
left disk and
right disk are offset in opposite directions
along a rotatable axis, creating a three-sphere illusion
when colored as light and shadow.
Composition Model
| Sub-object | Role | Color parameter | Drawn |
|---|---|---|---|
| centerDisk | Main subject; shows center-point marker | centerColor |
3rd (top) |
| leftDisk | Light side; offset in −axis direction | leftColor |
2nd |
| rightDisk | Shadow side; offset in +axis direction | rightColor |
1st (back) |
Geometry
All three disk centers lie on a common axis defined by
axisAngle (degrees CCW from +x axis; 0° = right, 90° = up).
Color Model
Each disk owns one SWColor applied to both fill and stroke.
Use the color setter methods to update. Alpha can be changed independently via the
alpha setters without replacing the color.
Typical Workflow
- Create the color objects (
SWColor.fromHex()). - Instantiate:
new SWTriDisk(center, r, k, offset, angle, cColor, lColor, rColor, thickness) - In
draw(): callrotate(speed * deltaT), thendrawOnGrid(grid). - Handle UI events with the setter methods.
- Call
reset()to restore all original values.
Constructor
new SWTriDisk(center, centerRadius, k, offset, axisAngle, centerColor, leftColor, rightColor, thickness)| Parameter | Type | Default | Description |
|---|---|---|---|
center | SWPoint | — | Center of the center disk (user coords) |
centerRadius | number | — | Radius of the center disk (user units, > 0) |
k | number | 0.9 | Side radius factor: sideRadius = k × centerRadius, k ∈ [0, 1] |
offset | number | 5 | Distance from center to each side disk center (user units) |
axisAngle | number | 0 | Axis orientation in degrees, CCW from +x axis (0 = right) |
centerColor | SWColor | undefined | Fill + stroke color for the center disk |
leftColor | SWColor | undefined | Fill + stroke color for the left disk |
rightColor | SWColor | undefined | Fill + stroke color for the right disk |
thickness | number | 2 | Stroke weight in pixels for all three disks |
Constructor examples
Properties
center SWPoint
Center of the center disk in user coordinates. Move the entire tri-disk by changing center.x / center.y.
centerRadius number
Radius of the center disk in user units. Prefer setRadius(r) to keep sub-objects in sync.
sideRadius getter (read-only)
Computed side disk radius: centerRadius × k.
k number
Side radius factor, k ∈ [0, 1]. Prefer setK(k) to recompute side radii immediately.
offset number
Distance (user units) from center to each side disk center along the axis. Prefer setOffset(offset).
axisAngle number
Current axis orientation in degrees, CCW from +x, normalized to [0, 360). Modified each frame by rotate().
thickness number
Stroke weight in pixels for all three disks. Prefer setThickness(w).
centerColor / leftColor / rightColor
SWColor | undefined
Color objects for each disk. Prefer the color setter methods to update and keep sub-objects in sync.
shouldShowCenter boolean
Whether the center-point dot is shown on the center disk. Prefer setShowCenter(bool).
centerDisk / leftDisk / rightDisk
SWDisk
Direct references to the three SWDisk sub-objects. Read-only in normal use; rebuilt by reset().
originalCenterRadius, originalK, originalOffset,
originalAxisAngle, originalThickness,
originalCenterColor, originalLeftColor, originalRightColor
various
Snapshot of constructor values used by reset(). Do not modify directly.
Methods
draw()Draws all three disks in screen (pixel) coordinates.
Use drawOnGrid() for user-coordinate rendering.
Drawing order: rightDisk → leftDisk → centerDisk (back to front).
drawOnGrid(grid)Draws all three disks mapped through the given SWGrid.
| Parameter | Type | Description |
|---|---|---|
grid | SWGrid | The coordinate grid for user-to-pixel mapping |
rotate(deltaAngle)Advances axisAngle by deltaAngle degrees
(CCW positive, CW negative). Result is normalized to [0, 360).
Call once per frame before drawOnGrid().
| Parameter | Type | Description |
|---|---|---|
deltaAngle | number | Degrees to advance the axis angle this frame |
setRadius(r)Sets the center disk radius and recomputes both side disk radii
(sideRadius = k × r).
setK(k)Sets the side-radius factor and immediately updates both side disk radii.
setOffset(offset)Sets the offset between center and side disk centers (user units). New positions take effect on the next draw call.
setAxisAngle(angle)Sets the axis angle (degrees, CCW from +x axis) normalized to [0, 360). Useful for snapping the axis to a fixed angle without animation.
setThickness(w)Sets the stroke weight in pixels on all three disks.
Sets both the fill and stroke color of the respective disk.
Copies the passed SWColor to avoid shared-reference side effects.
Sets the opacity (0–100) of the respective disk's fill and stroke without replacing its color.
setShowCenter(show = true)Shows (true) or hides (false) the center-point
marker dot on the center disk. Updates both shouldShowCenter and
the sub-object's flag.
reset()Restores all geometry, thickness, and colors to the values passed to the constructor. Rebuilds the three sub-objects cleanly. Does not move the center position.
static SWTriDisk.copy(other) → SWTriDiskReturns a deep copy of other — current state and
original snapshot — so the copy's reset() matches the original's.
toString() → stringReturns a compact debug string, e.g.:
SWTriDisk(center:(0.00, 0.00), centerRadius:5, k:0.9, offset:5, axisAngle:135.0°)
Animation Guide
Elapsed-time pattern (frame-rate independent)
Always compute elapsed time (deltaT) from millis() rather than
assuming a fixed frame rate.
Spin control
Axis-angle snap
Usage Examples
Minimal tri-disk
Fully colored, spinning tri-disk
Color and alpha control
Loading dependencies
Source Code
swTriDisk.js — full source
/*
File: swTriDisk.js
Date: 2026-03-30
Author: klp
App: SketchWaveTNT2026-03-19-Stg7
Purpose: SWTriDisk class for SketchWaveJS
SWTriDisk represents a composition of three collinear SWDisk objects:
- centerDisk : drawn on top (the main subject)
- leftDisk : offset in the −axis direction (light side by default)
- rightDisk : offset in the +axis direction (shadow side by default)
Geometry:
All three disk centers lie on a common line called the axis.
The axis is defined by axisAngle (degrees CCW from +x axis, 0 = right).
leftDisk center = center − offset × (cos(axisAngle), sin(axisAngle))
rightDisk center = center + offset × (cos(axisAngle), sin(axisAngle))
sideRadius = k × centerRadius, k ∈ [0, 1]
Drawing order (back → front): rightDisk, leftDisk, centerDisk.
This ensures the center always occludes the side disks regardless of color,
producing the shadow/light crescent illusion.
Animations:
rotate(delta) — advance axisAngle by delta degrees (CCW positive, CW negative).
Call once per frame before drawOnGrid() to spin the axis.
Color model:
Each disk has ONE SWColor applied to both fill and stroke.
Use setCenterColor / setLeftColor / setRightColor to update.
Alpha can be set independently with setCenterAlpha / setLeftAlpha / setRightAlpha.
Dependencies: p5.js, SWColor, SWPoint, SWGrid, SWDisk.
*/
console.log("[swTriDisk.js] SWTriDisk class loaded.");
class SWTriDisk {
/**
* @param {SWPoint} center - Center of the centerDisk (user coords)
* @param {number} centerRadius - Radius of the center disk (user units, > 0)
* @param {number} [k=0.9] - Side disk radius = k × centerRadius, k ∈ [0, 1]
* @param {number} [offset=5] - Distance from center to each side disk center (user units)
* @param {number} [axisAngle=0] - Axis orientation (degrees, CCW from +x axis; 0 = right)
* @param {SWColor} [centerColor] - Fill + stroke color for the center disk
* @param {SWColor} [leftColor] - Fill + stroke color for the left disk
* @param {SWColor} [rightColor] - Fill + stroke color for the right disk
* @param {number} [thickness=2] - Stroke weight for all three disks (pixels)
*/
constructor(center, centerRadius, k = 0.9, offset = 5, axisAngle = 0,
centerColor = undefined, leftColor = undefined, rightColor = undefined,
thickness = 2) {
this.center = center;
this.centerRadius = centerRadius;
this.k = k;
this.offset = offset;
this.axisAngle = axisAngle;
this.thickness = thickness;
// Always copy colors to avoid mutating shared SWColor objects
this.centerColor = centerColor ? SWColor.copy(centerColor) : undefined;
this.leftColor = leftColor ? SWColor.copy(leftColor) : undefined;
this.rightColor = rightColor ? SWColor.copy(rightColor) : undefined;
// Originals stored at construction — used by reset()
this.originalCenterRadius = centerRadius;
this.originalK = k;
this.originalOffset = offset;
this.originalAxisAngle = axisAngle;
this.originalThickness = thickness;
this.originalCenterColor = centerColor ? SWColor.copy(centerColor) : undefined;
this.originalLeftColor = leftColor ? SWColor.copy(leftColor) : undefined;
this.originalRightColor = rightColor ? SWColor.copy(rightColor) : undefined;
this.shouldShowCenter = true;
this._buildSubObjects();
}//end constructor
// ─── Convenience getter ────────────────────────────────────────────────────
/** Side disk radius (centerRadius × k) */
get sideRadius() {
return this.centerRadius * this.k;
}
// ─── Internal Construction ─────────────────────────────────────────────────
/**
* Creates the three SWDisk sub-objects from current property values.
* Called once at construction and again by reset().
*/
_buildSubObjects() {
const cx = this.center.x;
const cy = this.center.y;
const rad = SWTriDisk._toRadians(this.axisAngle);
const dx = this.offset * Math.cos(rad);
const dy = this.offset * Math.sin(rad);
const sRad = this.sideRadius;
// Center disk (with a visible center-point marker)
const cCenter = new SWPoint(cx, cy, undefined, 8, swDarkGray);
this.centerDisk = new SWDisk(
cCenter, this.centerRadius, this.thickness,
this.centerColor,
this.centerColor ? SWColor.copy(this.centerColor) : undefined
);
this.centerDisk.shouldShowCenter = this.shouldShowCenter;
// Left disk (offset in the −axis direction)
this.leftDisk = new SWDisk(
new SWPoint(cx - dx, cy - dy),
sRad, this.thickness,
this.leftColor,
this.leftColor ? SWColor.copy(this.leftColor) : undefined
);
this.leftDisk.shouldShowCenter = false;
// Right disk (offset in the +axis direction)
this.rightDisk = new SWDisk(
new SWPoint(cx + dx, cy + dy),
sRad, this.thickness,
this.rightColor,
this.rightColor ? SWColor.copy(this.rightColor) : undefined
);
this.rightDisk.shouldShowCenter = false;
}//end _buildSubObjects
/**
* Pushes the current center position, axisAngle, and offset into the
* three sub-object center coordinates. Called before every draw.
* Cheap: no allocations — just number assignments.
*/
_syncSubObjects() {
// Sync center disk position and center-marker flag
this.centerDisk.center.x = this.center.x;
this.centerDisk.center.y = this.center.y;
this.centerDisk.shouldShowCenter = this.shouldShowCenter;
// Recompute side disk centers from current geometry
const rad = SWTriDisk._toRadians(this.axisAngle);
const dx = this.offset * Math.cos(rad);
const dy = this.offset * Math.sin(rad);
this.leftDisk.center.x = this.center.x - dx;
this.leftDisk.center.y = this.center.y - dy;
this.rightDisk.center.x = this.center.x + dx;
this.rightDisk.center.y = this.center.y + dy;
}//end _syncSubObjects
// ─── Private helper ────────────────────────────────────────────────────────
/** Converts degrees to radians without depending on p5.js globals */
static _toRadians(degrees) {
return degrees * Math.PI / 180;
}
// ─── Drawing ───────────────────────────────────────────────────────────────
/**
* Draws the tri-disk group in screen (pixel) coordinates.
* Use drawOnGrid() for user-coordinate rendering.
* Drawing order: rightDisk → leftDisk → centerDisk (back to front).
*/
draw() {
this._syncSubObjects();
this.rightDisk.draw();
this.leftDisk.draw();
this.centerDisk.draw();
}//end draw
/**
* Draws the tri-disk group mapped through the given SWGrid.
* Drawing order: rightDisk → leftDisk → centerDisk (back to front).
* @param {SWGrid} grid
*/
drawOnGrid(grid) {
this._syncSubObjects();
this.rightDisk.drawOnGrid(grid);
this.leftDisk.drawOnGrid(grid);
this.centerDisk.drawOnGrid(grid);
}//end drawOnGrid
// ─── Rotation animation ────────────────────────────────────────────────────
/**
* Advances axisAngle by deltaAngle degrees (CCW positive, CW negative).
* Result is normalized to [0, 360).
* Call once per frame before draw/drawOnGrid to spin the axis.
* @param {number} deltaAngle
*/
rotate(deltaAngle) {
this.axisAngle = ((this.axisAngle + deltaAngle) % 360 + 360) % 360;
}//end rotate
// ─── Geometry setters ──────────────────────────────────────────────────────
/**
* Sets the center disk radius and recomputes both side disk radii.
* @param {number} r - user units
*/
setRadius(r) {
this.centerRadius = r;
if (this.centerDisk) this.centerDisk.setRadius(r);
const sRad = this.sideRadius;
if (this.leftDisk) this.leftDisk.setRadius(sRad);
if (this.rightDisk) this.rightDisk.setRadius(sRad);
}//end setRadius
/**
* Sets k (side radius factor) and recomputes side radii.
* @param {number} k - value in [0, 1]
*/
setK(k) {
this.k = k;
const sRad = this.sideRadius;
if (this.leftDisk) this.leftDisk.setRadius(sRad);
if (this.rightDisk) this.rightDisk.setRadius(sRad);
}//end setK
/**
* Sets the offset between the center disk and each side disk center.
* Side positions are recalculated on the next _syncSubObjects() call (i.e. next draw).
* @param {number} offset - user units
*/
setOffset(offset) {
this.offset = offset;
}//end setOffset
/**
* Sets the axis angle (degrees, CCW from +x axis; 0 = right).
* Normalized to [0, 360). Side positions are recalculated on the next _syncSubObjects().
* @param {number} angle
*/
setAxisAngle(angle) {
this.axisAngle = ((angle % 360) + 360) % 360;
}//end setAxisAngle
/**
* Sets the stroke weight (pixels) for all three disks.
* @param {number} w
*/
setThickness(w) {
this.thickness = w;
if (this.centerDisk) this.centerDisk.thickness = w;
if (this.leftDisk) this.leftDisk.thickness = w;
if (this.rightDisk) this.rightDisk.thickness = w;
}//end setThickness
// ─── Color setters ─────────────────────────────────────────────────────────
/**
* Sets the fill AND stroke color of the center disk.
* @param {SWColor} swColor
*/
setCenterColor(swColor) {
this.centerColor = swColor ? SWColor.copy(swColor) : undefined;
if (this.centerDisk) {
this.centerDisk.setFillColor(swColor);
this.centerDisk.setStrokeColor(swColor ? SWColor.copy(swColor) : undefined);
}
}//end setCenterColor
/**
* Sets the fill AND stroke color of the left disk.
* @param {SWColor} swColor
*/
setLeftColor(swColor) {
this.leftColor = swColor ? SWColor.copy(swColor) : undefined;
if (this.leftDisk) {
this.leftDisk.setFillColor(swColor);
this.leftDisk.setStrokeColor(swColor ? SWColor.copy(swColor) : undefined);
}
}//end setLeftColor
/**
* Sets the fill AND stroke color of the right disk.
* @param {SWColor} swColor
*/
setRightColor(swColor) {
this.rightColor = swColor ? SWColor.copy(swColor) : undefined;
if (this.rightDisk) {
this.rightDisk.setFillColor(swColor);
this.rightDisk.setStrokeColor(swColor ? SWColor.copy(swColor) : undefined);
}
}//end setRightColor
// ─── Alpha setters ─────────────────────────────────────────────────────────
/**
* Sets the opacity (0–100) of the center disk's fill and stroke.
* @param {number} alpha
*/
setCenterAlpha(alpha) {
if (this.centerDisk) {
this.centerDisk.setFillAlpha(alpha);
if (this.centerDisk.strokeColor) this.centerDisk.setStrokeAlpha(alpha);
}
}//end setCenterAlpha
/**
* Sets the opacity (0–100) of the left disk's fill and stroke.
* @param {number} alpha
*/
setLeftAlpha(alpha) {
if (this.leftDisk) {
this.leftDisk.setFillAlpha(alpha);
if (this.leftDisk.strokeColor) this.leftDisk.setStrokeAlpha(alpha);
}
}//end setLeftAlpha
/**
* Sets the opacity (0–100) of the right disk's fill and stroke.
* @param {number} alpha
*/
setRightAlpha(alpha) {
if (this.rightDisk) {
this.rightDisk.setFillAlpha(alpha);
if (this.rightDisk.strokeColor) this.rightDisk.setStrokeAlpha(alpha);
}
}//end setRightAlpha
// ─── Center marker ─────────────────────────────────────────────────────────
/**
* Shows or hides the center disk's center-point marker.
* @param {boolean} [show=true]
*/
setShowCenter(show = true) {
this.shouldShowCenter = show;
if (this.centerDisk) this.centerDisk.shouldShowCenter = show;
}//end setShowCenter
// ─── Reset ─────────────────────────────────────────────────────────────────
/**
* Resets all geometry, thickness, and colors to the originals stored
* at construction time. Does NOT move the center position.
*/
reset() {
this.centerRadius = this.originalCenterRadius;
this.k = this.originalK;
this.offset = this.originalOffset;
this.axisAngle = this.originalAxisAngle;
this.thickness = this.originalThickness;
// Restore color references
if (this.originalCenterColor) this.centerColor = SWColor.copy(this.originalCenterColor);
if (this.originalLeftColor) this.leftColor = SWColor.copy(this.originalLeftColor);
if (this.originalRightColor) this.rightColor = SWColor.copy(this.originalRightColor);
// Rebuild sub-objects cleanly from restored values
this._buildSubObjects();
}//end reset
// ─── Copy ──────────────────────────────────────────────────────────────────
/**
* Returns a deep copy of the given SWTriDisk.
* @param {SWTriDisk} other
* @returns {SWTriDisk}
*/
static copy(other) {
if (!(other instanceof SWTriDisk)) {
console.warn("SWTriDisk.copy: argument is not an SWTriDisk");
return null;
}
// Construct from originals so the copy's reset() behaves like the original's
const d = new SWTriDisk(
SWPoint.copy(other.center),
other.originalCenterRadius,
other.originalK,
other.originalOffset,
other.originalAxisAngle,
other.centerColor,
other.leftColor,
other.rightColor,
other.originalThickness
);
// Mirror current animated state
d.centerRadius = other.centerRadius;
d.k = other.k;
d.offset = other.offset;
d.axisAngle = other.axisAngle;
d.thickness = other.thickness;
d.setRadius(d.centerRadius);
d.shouldShowCenter = other.shouldShowCenter;
d._syncSubObjects();
return d;
}//end copy
// ─── toString ──────────────────────────────────────────────────────────────
toString() {
return `SWTriDisk(center:(${this.center.x.toFixed(2)}, ${this.center.y.toFixed(2)}), ` +
`centerRadius:${this.centerRadius}, k:${this.k}, ` +
`offset:${this.offset}, axisAngle:${this.axisAngle.toFixed(1)}°)`;
}//end toString
}//end SWTriDisk class