Overview
SWSun is a SketchWave composite class that draws a stylised sun (or moon) on a p5.js canvas. It combines a central SWDisk ball with up to 36 evenly-spaced radiating shapes, surrounded by a soft multi-layer glow. Four ray types are built in:
Straight stroke from ball edge outward (SWLine style)
SWArrow pointing outward with configurable tip shape
SWTriangle isosceles spike — tip outward, base at ball edge
Wavy line with amplitude tapering at both ends (default)
Features
- Ball — central filled & stroked disk; optional hide with
showBall - Glow — concentric semi-transparent rings; adjustable scale, layers, and alpha
- Ray colours — 1–5 colours cycle across rays for a multi-coloured corona effect
- Breathing —
breathe(sinusoid, t)scales ball + rays together via SWSinusoid - Rotation —
rotateRays(degPerSec, t)spins all rays continuously - Ripple —
animateSinusoid(phaseDegPerSec, t)makes sinusoid rays travel outward - Drag —
setCenter(cx, cy)lets you move the sun to any canvas position - Grid support —
drawOnGrid(grid)maps to user coordinates via SWGrid
SWDisk (ball), an array of
SWLine / SWArrow / SWTriangle rays,
and a custom inline sinusoid renderer. It does not extend
any parent class.
Constructor
let sun = new SWSun(cx, cy, ballRadius, ballFillColor, options);
Required Parameters
| Parameter | Type | Description |
|---|---|---|
cx |
number | Centre x — pixels when using draw(), user units when using drawOnGrid() |
cy |
number | Centre y — same coordinate system as cx |
ballRadius |
number | Radius of the central disk in the same units as cx/cy |
ballFillColor |
SWColor | Fill colour of the central ball. Also used as default for glow and rays if those options are omitted. |
Options Object
Pass any combination of these as a plain JS object in the fifth argument:
Ball Options
| Key | Type | Default | Description |
|---|---|---|---|
ballStrokeColor | SWColor | undefined | Border colour of the ball. Omit for no stroke. |
ballThickness | number | 2 | Border stroke weight in pixels. |
showBall | boolean | true | Whether to draw the central disk. Setting false hides the ball but keeps the glow and rays. |
Glow Options
| Key | Type | Default | Description |
|---|---|---|---|
showGlow | boolean | true | Draw the soft concentric glow rings. |
glowColor | SWColor | ballFillColor | Colour tint of the glow rings. |
glowScale | number | 2.5 | Outermost glow ring radius = glowScale × ballRadius. Increase for a wider aura. |
glowLayers | number | 5 | Number of concentric rings drawn. More layers = smoother gradient. |
glowAlpha | number | 35 | Peak alpha (0–100) of the innermost ring. Outermost ring fades to 0 automatically. |
Ray Options
| Key | Type | Default | Description |
|---|---|---|---|
showRays | boolean | true | Draw the radiating shapes. |
rayType | string | "sinusoid" | Ray shape. One of: "line", "arrow", "triangle", "sinusoid". |
rayCount | number | 8 | Number of evenly-spaced rays around the circle. |
rayLength | number | ballRadius × 1.5 | Length of each ray in the same units as cx/cy. |
rayThickness | number | 3 | Stroke weight for line, arrow, and sinusoid rays. |
rayColors | SWColor[] | [ballFillColor] | Array of 1–5 SWColor instances. Colours cycle across rays (ray 0 gets colour 0, ray 1 gets colour 1, etc., wrapping around). |
Sinusoid Ray Options
| Key | Type | Default | Description |
|---|---|---|---|
sinAmplitude | number | ballRadius × 0.25 | Wave amplitude in pixels. Amplitude tapers to zero at both ends of each ray. |
sinPeriods | number | 2.5 | Number of full sine-wave cycles along the ray length. |
rippleOutward | boolean | true | When true (default), waves travel away from the sun when animateSinusoid() is called. Set to false to reverse direction (waves travel toward the sun). |
Triangle Ray Options
| Key | Type | Default | Description |
|---|---|---|---|
triWidth | number | ballRadius × 0.5 | Width of the triangle base (at the ball edge). The tip always points outward. |
Arrow Ray Options
| Key | Type | Default | Description |
|---|---|---|---|
arrowTipAngle | number | 25 | Arrowhead half-angle in degrees. Larger = wider barbs. |
arrowTipFactor | number | 0.35 | Arrowhead barb length as a fraction (0–1) of the total ray length. |
Constructor Example
let sun;
function setup() {
createCanvas(640, 480);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
const fill = new SWColor(42, 90, 100, 100, "sunFill");
const stroke = new SWColor(25, 90, 80, 100, "sunStroke");
const glow = new SWColor(42, 80, 100, 100, "sunGlow");
const ray1 = new SWColor(42, 90, 100, 100, "ray1");
const ray2 = new SWColor(25, 90, 80, 100, "ray2");
sun = new SWSun(width / 2, height / 2, 60, fill, {
ballStrokeColor: stroke,
ballThickness: 3,
showGlow: true,
glowColor: glow,
glowScale: 2.5,
glowLayers: 5,
glowAlpha: 35,
rayType: 'sinusoid',
rayCount: 12,
rayLength: 140,
rayThickness: 3,
rayColors: [ray1, ray2],
sinAmplitude: 16,
sinPeriods: 2.5
});
}
function draw() {
background(200, 20, 95);
sun.draw();
}
Key Properties
After construction, the following properties are readable and can be modified via their setter methods. Directly setting a property bypasses the setter guard logic, so prefer setters for safety.
cx / cy
Type: number — Centre coordinates. Use setCenter(cx, cy) to move the sun.
ballRadius / originalBallRadius
Type: number — ballRadius is the current radius; originalBallRadius is the baseline that breathing animates around. Use setBallRadius(r) to update both at once.
_currentRadius / _currentRayLength
Type: number — Live animated values set by breathe(). Read these (not ballRadius) if you need the current on-screen size.
showBall / showGlow / showRays
Type: boolean — Visibility flags. Toggle these directly to show/hide each layer without rebuilding the object.
rayType
Type: string — One of "line", "arrow", "triangle", "sinusoid". Change via setRayType(type). Takes effect immediately on the next draw() call.
glowScale
Type: number — Outermost glow ring = glowScale × _currentRadius. When breathing is on, the glow radius breathes proportionally. The hit-test for mouse drag also uses _currentRadius × glowScale as the click radius.
rayColors
Type: SWColor[] — Array of 1–5 colours. Ray i uses rayColors[i % rayColors.length]. Use setRayColors(arr) to replace the array.
_rotationAngle
Type: number (degrees) — Angular offset added to every ray's base angle. Updated by rotateRays(); reset to 0 by reset().
sinPhase
Type: number (radians) — Phase offset added to the sinusoid wave for each sinusoid-type ray. Updated by animateSinusoid(); controls the ripple effect.
Methods
Drawing Methods
draw()
Renders the sun in screen (pixel) coordinates. Call once per draw() loop frame.
Rendering order (back to front): glow rings → rays → central ball.
function draw() {
background(200, 20, 95);
sun.draw();
}
drawOnGrid(grid)
Parameters: grid (SWGrid)
Maps cx, cy, and all radii/lengths from user coordinates to screen pixels using the provided SWGrid, then renders normally.
// sun.cx / sun.cy are in user (math) units
sun.drawOnGrid(myGrid);
Animation Methods
breathe(sinusoid, t)
Parameters: sinusoid (SWSinusoid), t (number — elapsed seconds)
Scales both the ball radius and the ray length by sinusoid.getValue(t). Configure the sinusoid with adjustWaveUsingExtrema(minScale, maxScale) to set the breathing range. The glow automatically breathes with the ball because glow radius is glowScale × _currentRadius.
let breatheSin;
function setup() {
// ...
breatheSin = new SWSinusoid();
breatheSin.adjustWaveUsingExtrema(0.75, 1.25); // 75% – 125% of base size
breatheSin.setPeriod(3); // 3-second cycle
}
function draw() {
let t = millis() / 1000;
sun.breathe(breatheSin, t);
sun.draw();
}
rotateRays(degPerSec, t)
Parameters: degPerSec (number), t (number — elapsed seconds)
Rotates all rays by degPerSec × t degrees from their original positions. Positive values rotate clockwise (screen coordinates). The ball and glow are not affected — only the ray angles change.
// Spin at 20°/sec clockwise
sun.rotateRays(20, totalRotTime);
// Spin counter-clockwise
sun.rotateRays(-30, totalRotTime);
animateSinusoid(phaseDegPerSec, t)
Parameters: phaseDegPerSec (number — phase advance rate in degrees/sec), t (number — elapsed seconds)
Advances sinPhase by radians(phaseDegPerSec) × t, which makes sinusoid-type rays appear to travel outward (ripple effect). Has no visual effect on other ray types.
// Ripple at 180°/sec
sun.animateSinusoid(180, totalRippleTime);
transform(options)
Combines breathing, rotation, and ripple in a single call. Mirrors the transform() API on SWLine, SWArrow, and SWTriangle.
| Option | Type | Default | Effect |
|---|---|---|---|
sinusoid | SWSinusoid | null | Breathing (null = skip) |
t | number | 0 | Time in seconds |
degPerSec | number | null | Rotation rate (null = skip) |
phaseDegPerSec | number | null | Ripple rate (null = skip) |
// All three animations in one call
sun.transform({
sinusoid: breatheSin,
t: totalTime,
degPerSec: 20,
phaseDegPerSec: 180
});
reset()
Resets all animation state to initial values: _currentRadius and _currentRayLength return to their originals, _rotationAngle returns to 0, and sinPhase returns to 0. Does not reset property changes made via setters (colours, counts, etc.).
sun.reset(); // stop animation effects and return to baseline
Setter Methods
| Method | Parameter | Notes |
|---|---|---|
setCenter(cx, cy) |
two numbers | Move the sun's centre. Safe to call every frame for mouse drag. |
setBallRadius(r) |
number | Updates ballRadius, originalBallRadius, _currentRadius, and the internal disk radius together. |
setBallFillColor(swColor) |
SWColor | Updates both the SWSun property and the internal SWDisk fill. |
setBallStrokeColor(swColor) |
SWColor | Updates both the SWSun property and the internal SWDisk stroke. |
setGlowColor(swColor) |
SWColor | |
setGlowAlpha(a) |
number 0–100 | Clamped to [0, 100]. |
setGlowScale(s) |
number | Minimum enforced at 1.05 (glow must extend beyond ball). |
setRayType(type) |
string | "line" | "arrow" | "triangle" | "sinusoid" |
setRayCount(count) |
number | Rounded to integer; minimum 0. |
setRayLength(len) |
number | Updates rayLength, originalRayLength, and _currentRayLength together. |
setRayThickness(w) |
number | Minimum 1. |
setRayColors(colorsArr) |
SWColor[] | Replaces the colours array; accepts 1–5 elements. |
setSinAmplitude(a) |
number | Minimum 0. Applies to sinusoid ray type only. |
setSinPeriods(p) |
number | Minimum 0.25. Applies to sinusoid ray type only. |
setRippleOutward(v) |
boolean | true = waves travel away from sun (default). false = waves travel toward sun. Takes effect immediately — no rebuild needed. |
setTriWidth(w) |
number | Minimum 1. Applies to triangle ray type only. |
Utility Methods
toString()
Returns: string — human-readable summary of the sun's state.
console.log(sun.toString());
// → "SWSun(cx:320.0, cy:240.0, r:60.0, rays:12×sinusoid)"
Ray Types in Detail
A straight stroke from the ball edge to the ray tip. Uses p5's line() with ROUND cap. The gap at the ball edge equals ballRadius + ballThickness/2 so the line starts just beyond the ball border. Clean and fast — good for a classic sun icon look.
Relevant options: rayThickness
Uses a SWArrow instance per ray — tail near the ball, arrowhead pointing outward. The arrowhead barb is drawn as two lines diverging from a point at arrowTipFactor × rayLength back from the tip. Good for "energy radiating outward" effects.
Relevant options: rayThickness, arrowTipAngle, arrowTipFactor
Uses a SWTriangle instance per ray — an isosceles spike with the tip pointing outward and the base at the ball edge. The base width is triWidth. Good for spiky sun / compass-rose styles. The fill colour matches the ray colour.
Relevant options: triWidth
Draws 40 vertex() points along each ray using inline wave math (sin(bFreq × d + sinPhase)). Amplitude tapers smoothly to zero at both the ball end and the tip via a sin(t × π) envelope. The animateSinusoid() method advances sinPhase over time, making the waves travel outward (ripple). Does not require a loaded SWSinusoid instance.
Relevant options: rayThickness, sinAmplitude, sinPeriods
Usage Examples
Example 1: Simple Static Sun
let sun;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
const yellow = new SWColor(42, 85, 100, 100, "yellow");
const orange = new SWColor(25, 90, 80, 100, "orange");
sun = new SWSun(200, 200, 50, yellow, {
ballStrokeColor: orange,
rayType: 'line',
rayCount: 12,
rayLength: 80
});
}
function draw() {
background(200, 30, 90);
sun.draw();
}
Example 2: Breathing Sun
let sun, breatheSin;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
const fill = new SWColor(42, 85, 100, 100, "fill");
sun = new SWSun(200, 200, 50, fill, {
rayType: 'sinusoid',
rayCount: 12,
rayLength: 80
});
breatheSin = new SWSinusoid();
breatheSin.adjustWaveUsingExtrema(0.80, 1.20);
breatheSin.setPeriod(3);
}
function draw() {
background(220, 20, 95);
const t = millis() / 1000;
sun.breathe(breatheSin, t);
sun.draw();
}
Example 3: Spinning Triangle-Ray Sun
let sun;
let rotElapsed = 0;
let lastT = 0;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
const gold = new SWColor(42, 90, 100, 100, "gold");
sun = new SWSun(200, 200, 55, gold, {
rayType: 'triangle',
rayCount: 8,
rayLength: 90,
triWidth: 20
});
}
function draw() {
background(220, 30, 90);
// Accumulate elapsed time for rotation
const nowT = millis() / 1000;
rotElapsed += (nowT - lastT);
lastT = nowT;
sun.rotateRays(30, rotElapsed); // 30°/sec CW
sun.draw();
}
Example 4: Rippling Sinusoid Rays
let sun;
let rippleElapsed = 0;
let lastT = 0;
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
const fill = new SWColor(50, 80, 100, 100, "fill");
sun = new SWSun(200, 200, 50, fill, {
rayType: 'sinusoid',
rayCount: 16,
rayLength: 100,
sinAmplitude: 10,
sinPeriods: 3
});
}
function draw() {
background(200, 20, 92);
const nowT = millis() / 1000;
rippleElapsed += (nowT - lastT);
lastT = nowT;
sun.animateSinusoid(180, rippleElapsed); // waves travel outward
sun.draw();
}
Example 5: Multi-colour Corona + Mouse Drag
let sun;
function setup() {
createCanvas(500, 500);
colorMode(HSB, 360, 100, 100, 100);
initializeSWColors();
const fill = new SWColor(42, 90, 100, 100, "fill");
const c1 = new SWColor(42, 90, 100, 100, "c1");
const c2 = new SWColor(25, 90, 80, 100, "c2");
const c3 = new SWColor(55, 80, 100, 100, "c3");
sun = new SWSun(250, 250, 60, fill, {
rayType: 'arrow',
rayCount: 12,
rayLength: 100,
rayColors: [c1, c2, c3], // 3 colours cycling
glowScale: 2.8,
glowAlpha: 40
});
}
function draw() {
background(215, 30, 88);
sun.draw();
}
function mouseDragged() {
// Drag the sun anywhere on the canvas
if (dist(mouseX, mouseY, sun.cx, sun.cy) <= sun._currentRadius * sun.glowScale) {
sun.setCenter(mouseX, mouseY);
}
}
Example 6: Time-of-Day Moon (showBall hidden)
// A "moon" — glow and sinusoid rays visible, no solid disk
const silver = new SWColor(210, 15, 92, 100, "silver");
const blueGlow = new SWColor(220, 30, 90, 100, "blueGlow");
moon = new SWSun(cx, cy, 55, silver, {
showBall: false, // hide the disk — glow shines through
showGlow: true,
glowColor: blueGlow,
glowScale: 3.0,
glowAlpha: 50,
rayType: 'sinusoid',
rayCount: 20,
rayLength: 110,
sinAmplitude: 8,
rayColors: [silver]
});
Integration with SketchWave
Loading Order
SWSun must be loaded after all of its dependencies:
<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.js"></script>
<!-- SketchWave dependencies (order matters) -->
<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/swDisk.js"></script>
<script src="../shapeClasses/swLine.js"></script>
<script src="../shapeClasses/swArrow.js"></script>
<script src="../shapeClasses/swTriangle.js"></script>
<script src="../shapeClasses/swSun.js"></script>
<!-- Your sketch -->
<script src="../sketches/yourSketch.js"></script>
"sinusoid" ray type computes its wave
inline — it does not create SWSinusoid instances. However,
swSinusoid.js must still be loaded before SWSun
because the breathe() method accepts a SWSinusoid object.
colorMode Requirement
SWSun relies on SWColor which requires p5.js to be in HSB mode:
function setup() {
createCanvas(width, height);
colorMode(HSB, 360, 100, 100, 100); // required
initializeSWColors(); // required (populates predefined SW colours)
// ...
}
Animation Timer Pattern
SWSun animations take an accumulated elapsed-time parameter rather than raw millis(). This lets you pause animations cleanly:
let rotationOn = false;
let totalRotTime = 0;
let lastRotSnap = 0; // millis() when rotation was last on
function draw() {
background(220, 20, 95);
if (rotationOn) {
const now = millis() / 1000;
totalRotTime += (now - lastRotSnap);
lastRotSnap = now;
sun.rotateRays(20, totalRotTime);
}
sun.draw();
}
function toggleRotation() {
rotationOn = !rotationOn;
if (rotationOn) {
lastRotSnap = millis() / 1000; // reset delta reference on resume
}
}
Using with SWSinusoid for Breathing
Build the sinusoid in setup() and call breathe() each frame with the elapsed time:
let breatheSin;
let breatheStart = 0;
function setup() {
// ...
breatheSin = new SWSinusoid();
breatheSin.adjustWaveUsingExtrema(0.75, 1.25);
breatheSin.setPeriod(3);
}
function draw() {
background(220, 20, 95);
const elapsed = (millis() / 1000) - breatheStart;
sun.breathe(breatheSin, elapsed);
sun.draw();
}
// Pause/resume breathing by saving elapsed before pause
// and subtracting pause duration from breatheStart on resume.
Source Code
Show / Hide Source Code
/*
File: swSun.js
Date: 2026-05-18
Author: klp
Workspace: SketchWaveTNT2026-05-01-Stg9
Purpose: SWSun class — a styled sun (or moon) with a central SWDisk ball,
optional radiating shapes, a soft multi-layer glow, and support
for breathing, rotation, and sinusoid ripple animations.
Dependencies: p5.js, swColor.js, swPoint.js, swDisk.js,
swLine.js, swArrow.js, swTriangle.js
*/
console.log("[swSun.js] SWSun class loaded.");
class SWSun {
constructor(cx, cy, ballRadius, ballFillColor, options = {}) {
this.cx = cx;
this.cy = cy;
this.ballRadius = ballRadius;
this.originalBallRadius = ballRadius;
// ── Ball styling ──────────────────────────────────────────────────
this.ballFillColor = ballFillColor ? SWColor.copy(ballFillColor) : undefined;
this.ballStrokeColor = options.ballStrokeColor ? SWColor.copy(options.ballStrokeColor) : undefined;
this.ballThickness = options.ballThickness ?? 2;
// ── Ball visibility ────────────────────────────────────────────────
this.showBall = options.showBall ?? true;
// ── Glow ──────────────────────────────────────────────────────────
this.showGlow = options.showGlow ?? true;
this.glowColor = options.glowColor
? SWColor.copy(options.glowColor)
: (ballFillColor ? SWColor.copy(ballFillColor) : undefined);
this.glowScale = options.glowScale ?? 2.5;
this.glowLayers = options.glowLayers ?? 5;
this.glowAlpha = options.glowAlpha ?? 35;
// ── Ray styling ───────────────────────────────────────────────────
this.showRays = options.showRays ?? true;
this.rayType = options.rayType ?? "sinusoid";
this.rayCount = options.rayCount ?? 8;
this.rayLength = options.rayLength ?? ballRadius * 1.5;
this.originalRayLength = this.rayLength;
this.rayThickness = options.rayThickness ?? 3;
if (options.rayColors && options.rayColors.length > 0) {
this.rayColors = options.rayColors.slice(0, 5).map(c => c ? SWColor.copy(c) : undefined);
} else {
this.rayColors = [ballFillColor ? SWColor.copy(ballFillColor) : undefined];
}
// ── Sinusoid ray options ──────────────────────────────────────────
this.sinAmplitude = options.sinAmplitude ?? ballRadius * 0.25;
this.sinPeriods = options.sinPeriods ?? 2.5;
this.sinPhase = 0;
this.rippleOutward = options.rippleOutward ?? true; // true = waves travel away from sun
// ── Triangle ray options ──────────────────────────────────────────
this.triWidth = options.triWidth ?? ballRadius * 0.5;
// ── Arrow ray options ─────────────────────────────────────────────
this.arrowTipAngle = options.arrowTipAngle ?? 25;
this.arrowTipFactor = options.arrowTipFactor ?? 0.35;
// ── Internal animation state ──────────────────────────────────────
this._rotationAngle = 0;
this._currentRadius = ballRadius;
this._currentRayLength = this.rayLength;
this._buildDisk();
}//end constructor
_buildDisk() {
const center = new SWPoint(this.cx, this.cy, undefined, 0, this.ballStrokeColor);
this.disk = new SWDisk(
center, this._currentRadius,
this.ballThickness, this.ballFillColor, this.ballStrokeColor
);
this.disk.shouldShowCenter = false;
}
draw() {
this._drawAtPosition(this.cx, this.cy, this._currentRadius, this._currentRayLength);
}
drawOnGrid(grid) {
const { x: sx, y: sy } = grid.userToScreen(this.cx, this.cy);
const sr = this._currentRadius * grid.xScale;
const srl = this._currentRayLength * grid.xScale;
this._drawAtPosition(sx, sy, sr, srl);
}
_drawAtPosition(cx, cy, ballR, rayLen) {
if (this.showGlow) this._drawGlow(cx, cy, ballR);
if (this.showRays && this.rayCount > 0 && rayLen > 0) this._drawRays(cx, cy, ballR, rayLen);
this.disk.center.x = cx;
this.disk.center.y = cy;
this.disk.radius = ballR;
if (this.showBall) {
fill(0, 0, 0, 0);
this.disk.draw();
}
}
_drawGlow(cx, cy, ballR) {
const gc = this.glowColor;
if (!gc) return;
push();
noStroke();
for (let i = 0; i < this.glowLayers; i++) {
const t = (this.glowLayers > 1) ? i / (this.glowLayers - 1) : 1;
const r = ballR * lerp(this.glowScale, 1.08, t);
const a = lerp(0, this.glowAlpha, t);
if (a > 0.4) { fill(gc.h, gc.s, gc.b, a); ellipse(cx, cy, r * 2, r * 2); }
}
pop();
}
_drawRays(cx, cy, ballR, rayLen) {
const angleStep = 360 / this.rayCount;
for (let i = 0; i < this.rayCount; i++) {
const angleDeg = i * angleStep + this._rotationAngle;
const col = this.rayColors[i % this.rayColors.length];
this._drawSingleRay(cx, cy, ballR, rayLen, angleDeg, col);
}
}
_drawSingleRay(cx, cy, ballR, rayLen, angleDeg, swColor) {
switch (this.rayType) {
case "line": this._drawLineRay (cx, cy, ballR, rayLen, angleDeg, swColor); break;
case "arrow": this._drawArrowRay (cx, cy, ballR, rayLen, angleDeg, swColor); break;
case "triangle": this._drawTriangleRay(cx, cy, ballR, rayLen, angleDeg, swColor); break;
case "sinusoid":
default: this._drawSinusoidRay(cx, cy, ballR, rayLen, angleDeg, swColor); break;
}
}
_drawLineRay(cx, cy, ballR, rayLen, angleDeg, swColor) {
const ar = radians(angleDeg);
const gap = ballR + this.ballThickness * 0.5;
push();
if (swColor && swColor.col) stroke(swColor.col); else noStroke();
strokeWeight(this.rayThickness);
strokeCap(ROUND);
noFill();
line(cx + cos(ar)*gap, cy + sin(ar)*gap,
cx + cos(ar)*(gap+rayLen), cy + sin(ar)*(gap+rayLen));
pop();
}
_drawArrowRay(cx, cy, ballR, rayLen, angleDeg, swColor) {
const ar = radians(angleDeg);
const gap = ballR + this.ballThickness * 0.5;
const ptA = new SWPoint(cx+cos(ar)*gap, cy+sin(ar)*gap, undefined, 0, swColor);
const ptB = new SWPoint(cx+cos(ar)*(gap+rayLen),cy+sin(ar)*(gap+rayLen),undefined, 0, swColor);
push();
const arrow = new SWArrow(ptA, ptB, this.rayThickness, swColor,
this.arrowTipAngle, this.arrowTipFactor);
arrow.shouldShowMidpoint = false;
arrow.shouldShowTailPoint = false;
arrow.draw();
pop();
}
_drawTriangleRay(cx, cy, ballR, rayLen, angleDeg, swColor) {
const ar = radians(angleDeg);
const perp = ar + HALF_PI;
const gap = ballR + this.ballThickness * 0.5;
const halfW = this.triWidth / 2;
const tipX = cx + cos(ar) * (gap + rayLen);
const tipY = cy + sin(ar) * (gap + rayLen);
const baseX = cx + cos(ar) * gap;
const baseY = cy + sin(ar) * gap;
const ptTip = new SWPoint(tipX, tipY);
const ptLeft = new SWPoint(baseX + cos(perp)*halfW, baseY + sin(perp)*halfW);
const ptRight = new SWPoint(baseX - cos(perp)*halfW, baseY - sin(perp)*halfW);
push();
const fillC = swColor || new SWColor(55, 80, 100, 100, "rayFill");
const tri = new SWTriangle(ptTip, ptLeft, ptRight, fillC,
{ strokeColor:fillC, strokeWeight:1,
showVertices:false, showCentroid:false });
tri.draw();
pop();
}
_drawSinusoidRay(cx, cy, ballR, rayLen, angleDeg, swColor) {
const ar = radians(angleDeg);
const perp = ar + HALF_PI;
const gap = ballR + this.ballThickness * 0.5;
const nPts = 40;
const bFreq = (TWO_PI * this.sinPeriods) / rayLen;
push();
if (swColor && swColor.col) stroke(swColor.col); else noStroke();
strokeWeight(this.rayThickness);
strokeCap(ROUND);
strokeJoin(ROUND);
noFill();
beginShape();
for (let i = 0; i <= nPts; i++) {
const t = i / nPts;
const d = gap + t * rayLen;
const taper = sin(t * PI);
const phaseSign = this.rippleOutward ? -1 : 1;
const offset = sin(bFreq * t * rayLen + phaseSign * this.sinPhase) * this.sinAmplitude * taper;
vertex(cx + cos(ar)*d + cos(perp)*offset,
cy + sin(ar)*d + sin(perp)*offset);
}
endShape();
pop();
}
breathe(sinusoid, t) {
const scale = sinusoid.getValue(t);
this._currentRadius = this.originalBallRadius * scale;
this._currentRayLength = this.originalRayLength * scale;
this.disk.radius = this._currentRadius;
}
rotateRays(degPerSec, t) { this._rotationAngle = degPerSec * t; }
animateSinusoid(phaseDegPerSec, t) { this.sinPhase = radians(phaseDegPerSec) * t; }
transform({ sinusoid=null, t=0, degPerSec=null, phaseDegPerSec=null } = {}) {
if (sinusoid !== null) this.breathe(sinusoid, t);
if (degPerSec !== null) this.rotateRays(degPerSec, t);
if (phaseDegPerSec !== null) this.animateSinusoid(phaseDegPerSec, t);
}
reset() {
this._currentRadius = this.originalBallRadius;
this._currentRayLength = this.originalRayLength;
this._rotationAngle = 0;
this.sinPhase = 0;
this.disk.radius = this.originalBallRadius;
}
setCenter(cx, cy) { this.cx = cx; this.cy = cy; }
setBallRadius(r) {
this.ballRadius = this.originalBallRadius = this._currentRadius = r;
this.disk.setRadius(r);
}
setBallFillColor(swColor) { this.ballFillColor = swColor ? SWColor.copy(swColor) : undefined; this.disk.setFillColor(swColor); }
setBallStrokeColor(swColor) { this.ballStrokeColor = swColor ? SWColor.copy(swColor) : undefined; this.disk.setStrokeColor(swColor); }
setGlowColor(swColor) { this.glowColor = swColor ? SWColor.copy(swColor) : undefined; }
setGlowAlpha(a) { this.glowAlpha = constrain(a, 0, 100); }
setGlowScale(s) { this.glowScale = max(1.05, s); }
setRayType(type) { this.rayType = type; }
setRayCount(count) { this.rayCount = max(0, Math.round(count)); }
setRayLength(len) {
this.rayLength = this.originalRayLength = this._currentRayLength = len;
}
setRayThickness(w) { this.rayThickness = max(1, w); }
setRayColors(arr) { if (arr && arr.length) this.rayColors = arr.slice(0,5).map(c => c ? SWColor.copy(c) : undefined); }
setSinAmplitude(a) { this.sinAmplitude = max(0, a); }
setSinPeriods(p) { this.sinPeriods = max(0.25, p); }
setRippleOutward(v) { this.rippleOutward = !!v; }
setTriWidth(w) { this.triWidth = max(1, w); }
toString() {
return `SWSun(cx:${this.cx.toFixed(1)}, cy:${this.cy.toFixed(1)}, ` +
`r:${this.ballRadius.toFixed(1)}, rays:${this.rayCount}×${this.rayType})`;
}
}//end class SWSun