SWSnowFlake Class Reference

A procedural crystalline shape that supports both iterative and recursive arm generation.

Last update:

Overview

SWSnowFlake is a Core composite class that builds a snowflake around a center locator. The center can be either:

  • SWPoint for static positioning
  • SWBug for animated movement modes in demos/apps

The class supports two algorithm families:

  • IterativeStellar Dendrite, Fern Dendrite, Plate Crystal, Needle Cluster
  • RecursiveKoch Crystal, Recursive Dendrite

In addition to style selection, you can control radius, arm count, colors, border behavior, center marker display, recursion controls, and both static and animated rotation.

Constructor

let flake = new SWSnowFlake(center, maxRadius, armCount, flakeColor, borderColor, options);

let center = new SWPoint(0, 0);
let flakeColor = new SWColor(195, 20, 98, 100, "flakeColor");
let borderColor = new SWColor(210, 35, 60, 95, "flakeBorder");

let flake = new SWSnowFlake(center, 6, 6, flakeColor, borderColor, {
    rotationDeg: 0,
    recursionDepth: 3,
    recursiveScale: 0.58,
    recursiveBranchAngleDeg: 33
});

Notes: arm count is clamped to a valid range; recursive settings are clamped for stability/performance.

Properties

center
Center locator object (SWPoint or SWBug).
maxRadius
Maximum arm reach in user units.
armCount
Number of radial arms used to generate the snowflake.
style
Current style key (iterative or recursive family).
flakeColor / borderColor
Primary stroke/fill color and border color.
borderEnabled / borderThickness
Outline toggle and thickness control.
rotationDeg / totalRotationDeg
Static rotation and combined static+animated rotation readout.
recursionDepth / recursiveScale / recursiveBranchAngleDeg
Fractal controls used by recursive styles.
shouldShowCenter / centerMarkerSize
Center marker visibility and marker size.

Methods

Shape + Style

setCenter(center)
Replace center object safely (supports SWPoint and SWBug).
setMaxRadius(r)
Update arm reach/radius.
setArmCount(n)
Update arm count (clamped).
setStyle(styleName)
Switch among allowed style names.
setSeed(seed)
Set deterministic pseudo-random seed for style jitter/details.

Fractal Controls

setRecursionDepth(n)
Set recursive level depth for Koch and recursive dendrite styles.
setRecursiveScale(scale)
Set branch length shrink factor for recursive dendrite.
setRecursiveBranchAngleDeg(angleDeg)
Set branch split angle in degrees.

Color + Border

setFlakeColor(swColor)
Set main snowflake color.
setBorderColor(swColor)
Set border color.
setBorderEnabled(shouldEnable)
Show/hide border outline layer.
setBorderThickness(t)
Set border thickness.

Rotation + Center Marker

setRotationDeg(angleDeg)
Set static orientation angle.
rotateBy(angleDeltaDeg)
Add delta angle to static rotation.
setAnimatedRotationDeg(angleDeg)
Set animated rotation contribution.
clearAnimatedRotation()
Reset animated contribution to 0.
setShowCenter(show)
Show/hide center marker.
setCenterMarkerSize(sz)
Set center marker size.

Rendering

draw()
Draw using current screen coordinates.
drawOnGrid(grid)
Draw in user coordinates using SWGrid transforms.
toString()
Debug summary string (style, arms, rotation, depth, border).

Allowed Styles

SWSnowFlake.allowedStyles();
// [
//   "stellarDendrite",
//   "fernDendrite",
//   "plateCrystal",
//   "needleCluster",
//   "kochCrystal",
//   "recursiveDendrite"
// ]

Usage Examples

Example 1: Basic Snowflake On Grid

let grid;
let snowflake;

function setup() {
    createCanvas(540, 540);
    colorMode(HSB, 360, 100, 100, 100);
    initializeSWColors();

    grid = new SWGrid({ UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10) });

    let center = new SWPoint(0, 0);
    let flakeColor = new SWColor(198, 18, 98, 100, "flake");
    let borderColor = new SWColor(210, 40, 60, 100, "border");

    snowflake = new SWSnowFlake(center, 6, 6, flakeColor, borderColor, {
        rotationDeg: 0,
        recursionDepth: 2,
        recursiveScale: 0.58,
        recursiveBranchAngleDeg: 33
    });

    snowflake.setStyle("stellarDendrite");
    snowflake.setBorderThickness(0.8);
    snowflake.setShowCenter(true);
}

function draw() {
    background(210, 30, 12, 100);
    grid.draw();
    snowflake.drawOnGrid(grid);
}

Example 2: Recursive Koch Crystal

// Switch to recursive Koch and increase depth
snowflake.setStyle("kochCrystal");
snowflake.setRecursionDepth(4);
snowflake.setRotationDeg(15);

Example 3: Animated Spin + Moving Center Bug

let bugCenter = new SWBug(0, 0, "p", 0.08, swWhite, {
    noiseAmount: 0.01,
    strokeWeight: 5
});

snowflake.setCenter(bugCenter);

function draw() {
    background(210, 30, 12, 100);
    grid.draw();

    bugCenter.move(grid);
    snowflake.setAnimatedRotationDeg(millis() * 0.03);
    snowflake.drawOnGrid(grid);
}

Integration Notes

Load scripts in dependency order:

<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.js"></script>
<script src="../shapeClasses/swColor.js"></script>
<script src="../shapeClasses/swPoint.js"></script>
<script src="../shapeClasses/swGrid.js"></script>
<script src="../shapeClasses/swBug.js"></script>
<script src="../shapeClasses/swSnowFlake.js"></script>
<script src="../sketches/yourSketch.js"></script>

Source Code

The complete SWSnowFlake class implementation:

Show/Hide Source Code
/*
File: swSnowFlake.js
Date: 2026-05-28
Author: klp + Copilot
Purpose: SWSnowFlake class for SketchWaveJS

A composite procedural snowflake centered on an SWPoint (or SWBug), with
configurable radius, arm count, style, color, and optional border outlines.

Dependencies:
- p5.js
- SWColor
- SWPoint
- SWBug (optional center type)
*/

console.log("[swSnowFlake.js] SWSnowFlake class loaded.");

class SWSnowFlake {
    constructor(center, maxRadius = 6, armCount = 6, flakeColor = undefined, borderColor = undefined, options = {}) {
        if (typeof options === "number") {
            options = { rotationDeg: options };
        }

        this.center = SWSnowFlake.copyCenter(center);
        this.maxRadius = Math.max(0.2, Number(maxRadius) || 6);
        this.armCount = SWSnowFlake.clampArmCount(armCount);

        this.flakeColor = SWSnowFlake.copyColorOrDefault(flakeColor, new SWColor(200, 20, 100, 100, "flakeColor"));
        this.borderColor = SWSnowFlake.copyColorOrDefault(borderColor, new SWColor(210, 35, 65, 100, "flakeBorder"));

        this.borderEnabled = true;
        this.borderThickness = 0.8;
        this.style = "stellarDendrite";
        this.seed = 1;
        this.rotationDeg = Number(options.rotationDeg ?? options.rotation ?? 0) || 0;
        this._animRotDeg = Number(options.animRotationDeg ?? options.animatedRotationDeg ?? 0) || 0;
        this.recursionDepth = SWSnowFlake.clampRecursionDepth(options.recursionDepth ?? 3);
        this.recursiveScale = SWSnowFlake.clampRecursiveScale(options.recursiveScale ?? 0.58);
        this.recursiveBranchAngleDeg = SWSnowFlake.clampRecursiveBranchAngle(options.recursiveBranchAngleDeg ?? 33);

        this.shouldShowCenter = false;
        this.centerMarkerSize = 5;
    }//end constructor

    static allowedStyles() {
        return ["stellarDendrite", "fernDendrite", "plateCrystal", "needleCluster", "kochCrystal", "recursiveDendrite"];
    }//end allowedStyles

    static clampArmCount(n) {
        return Math.max(4, Math.min(16, Math.round(Number(n) || 6)));
    }//end clampArmCount

    static clampRecursionDepth(n) {
        return Math.max(0, Math.min(6, Math.round(Number(n) || 0)));
    }//end clampRecursionDepth

    static clampRecursiveScale(n) {
        const x = Number(n);
        if (!Number.isFinite(x)) return 0.58;
        return Math.max(0.35, Math.min(0.82, x));
    }//end clampRecursiveScale

    static clampRecursiveBranchAngle(n) {
        const x = Number(n);
        if (!Number.isFinite(x)) return 33;
        return Math.max(8, Math.min(85, x));
    }//end clampRecursiveBranchAngle

    static copyCenter(center) {
        if (!(center instanceof SWPoint)) {
            throw new Error("SWSnowFlake expects center to be an SWPoint or SWBug.");
        }

        if (typeof SWBug !== "undefined" && center instanceof SWBug) {
            const strokeColorCopy = center.strokeColor ? SWColor.copy(center.strokeColor) : undefined;
            const bugCopy = new SWBug(center.x, center.y, center.mode, center.speed, strokeColorCopy, {
                noiseAmount: center.noiseAmount,
                strokeWeight: center.strokeWeight,
                trailPersistence: center.trailPersistence,
                shouldWrapAround: center.shouldWrapAround,
            });

            bugCopy.label = center.label;
            bugCopy.showLabel = center.showLabel;
            bugCopy.labelOffset = { x: center.labelOffset.x, y: center.labelOffset.y };
            bugCopy.labelFontSize = center.labelFontSize;
            bugCopy.isDraggable = center.isDraggable;
            bugCopy.penOn = center.penOn;
            bugCopy.maxTrailLength = center.maxTrailLength;
            bugCopy.trail = center.trail.map(function (pt) {
                return { x: pt.x, y: pt.y, z: pt.z };
            });
            bugCopy.setShouldShowTrails(!!center.shouldShowTrails);

            return bugCopy;
        }

        return SWPoint.copy(center);
    }//end copyCenter

    static copyColorOrDefault(swColor, fallbackColor) {
        if (swColor instanceof SWColor) {
            return SWColor.copy(swColor);
        }
        return SWColor.copy(fallbackColor);
    }//end copyColorOrDefault

    static hash01(a, b = 0, c = 0) {
        const x = Math.sin(a * 12.9898 + b * 78.233 + c * 37.719) * 43758.5453;
        return x - Math.floor(x);
    }//end hash01

    setCenter(center) {
        if (center instanceof SWPoint) {
            this.center = SWSnowFlake.copyCenter(center);
        }
    }//end setCenter

    setMaxRadius(r) {
        this.maxRadius = Math.max(0.2, Number(r) || this.maxRadius);
    }//end setMaxRadius

    setArmCount(n) {
        this.armCount = SWSnowFlake.clampArmCount(n);
    }//end setArmCount

    setFlakeColor(swColor) {
        if (swColor instanceof SWColor) this.flakeColor = SWColor.copy(swColor);
    }//end setFlakeColor

    setBorderColor(swColor) {
        if (swColor instanceof SWColor) this.borderColor = SWColor.copy(swColor);
    }//end setBorderColor

    setBorderEnabled(shouldEnable) {
        this.borderEnabled = !!shouldEnable;
    }//end setBorderEnabled

    setBorderThickness(t) {
        this.borderThickness = Math.max(0, Number(t) || 0);
    }//end setBorderThickness

    setStyle(styleName) {
        if (SWSnowFlake.allowedStyles().includes(styleName)) this.style = styleName;
    }//end setStyle

    setSeed(seed) {
        this.seed = Math.max(0, Math.round(Number(seed) || 0));
    }//end setSeed

    setRotationDeg(angleDeg) {
        const n = Number(angleDeg);
        this.rotationDeg = Number.isFinite(n) ? n : this.rotationDeg;
    }//end setRotationDeg

    setRecursionDepth(n) {
        this.recursionDepth = SWSnowFlake.clampRecursionDepth(n);
    }//end setRecursionDepth

    setRecursiveScale(scale) {
        this.recursiveScale = SWSnowFlake.clampRecursiveScale(scale);
    }//end setRecursiveScale

    setRecursiveBranchAngleDeg(angleDeg) {
        this.recursiveBranchAngleDeg = SWSnowFlake.clampRecursiveBranchAngle(angleDeg);
    }//end setRecursiveBranchAngleDeg

    rotateBy(angleDeltaDeg) {
        const n = Number(angleDeltaDeg);
        if (Number.isFinite(n)) {
            this.rotationDeg += n;
        }
    }//end rotateBy

    setAnimatedRotationDeg(angleDeg) {
        const n = Number(angleDeg);
        this._animRotDeg = Number.isFinite(n) ? n : this._animRotDeg;
    }//end setAnimatedRotationDeg

    clearAnimatedRotation() {
        this._animRotDeg = 0;
    }//end clearAnimatedRotation

    get totalRotationDeg() {
        return this.rotationDeg + this._animRotDeg;
    }//end totalRotationDeg

    setShowCenter(show) {
        this.shouldShowCenter = !!show;
    }//end setShowCenter

    setCenterMarkerSize(sz) {
        this.centerMarkerSize = Math.max(1, Number(sz) || this.centerMarkerSize);
    }//end setCenterMarkerSize

    draw() {
        this._drawAt(this.center.x, this.center.y, this.maxRadius);
    }//end draw

    drawOnGrid(grid) {
        if (!grid || typeof grid.userToScreen !== "function") return;
        const pos = grid.userToScreen(this.center.x, this.center.y);
        const rx = this.maxRadius * grid.xScale;
        const ry = this.maxRadius * grid.yScale;
        const rScreen = (rx + ry) / 2;
        this._drawAt(pos.x, pos.y, rScreen);
    }//end drawOnGrid

    // Full drawing helpers omitted here for page brevity.
    // See shapeClasses/swSnowFlake.js for the full method set.

    toString() {
        return `SWSnowFlake(center: ${this.center.toString()}, maxRadius: ${this.maxRadius}, arms: ${this.armCount}, style: ${this.style}, rotation: ${this.totalRotationDeg.toFixed(1)}deg, depth: ${this.recursionDepth}, borderEnabled: ${this.borderEnabled})`;
    }//end toString
}//end class SWSnowFlake