Sketch 2026-06-08 16:23 (Remix) (Remix) (Remix)

This sketch generates a symmetrical Islamic rosette pattern with customizable petals and central stars. The design uses trigonometric calculations to create intricate geometric patterns radiating from the center, with adjustable point count and size controlled by interactive sliders.

🧪 Try This!

Experiment with the code by making these changes:

  1. Change the background color — Altering the background color instantly changes the canvas from white to any color, making the petals stand out differently.
  2. Make the petals blue — Changing the stroke color of the petals from red to blue creates a completely different color scheme.
  3. Double the petal thickness
  4. Make petals unfilled with dark outlines — Changing the stroke color in the Rosette display method to dark blue creates higher contrast against a light background.
  5. Disable the central stars — Commenting out the displayStars() call removes all the ornamental stars from the center, leaving just the petals.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch renders an Islamic rosette—a classic geometric pattern used in Islamic art and architecture. The rosette consists of a ring of petals arranged symmetrically around a center, each petal layered with concentric star shapes in different colors. The design is entirely procedural: the petals' curves and the stars' radii are calculated from trigonometric formulas based on the number of points and rosette size. Two sliders let you instantly adjust the point count (8 to 60) and overall rosette size to explore how geometry changes the visual impact.

The code is organized around two main classes: Rosette, which calculates petal geometry and manages a collection of Star objects, and Star, which draws star polygons with alternating inner and outer vertices. The sketch demonstrates object-oriented design, class composition, trigonometric geometry, and interactive parameter control—all essential skills for procedural design and mathematical art.

⚙️ How It Works

  1. When the sketch loads, setup() creates a 1000×1000 canvas in degrees mode and instantiates two interactive sliders: one controls the number of points (8–60) and another controls the rosette size (125–500 pixels). The noLoop() call means the canvas only redraws when a slider changes.
  2. When a slider moves, updateSketch() reads the new values and creates currentNPoints rosette objects plus one central rosette. Each Rosette constructor pre-calculates dozens of trigonometric values—sines, cosines, tangents of half-angles—and uses them to compute petal point coordinates and the radii for seven inner Star objects.
  3. The draw() function translates to the canvas center and rotates through each rosette point, drawing rotated copies of the same petal shape in different colors from a palette. The petal is a six-pointed polygon defined by pre-calculated coordinates stored in this.petalPoints.
  4. After all petals are drawn, displayStars() draws seven concentric star shapes at the center. Each Star object stores two radii (inner and outer) and draws vertices that alternate between them, creating the classic pointed-star silhouette.
  5. The sketch uses a safeDivide() helper function to prevent division-by-zero errors when calculating geometry for unusual point counts, making the pattern robust across the full 8–60 point range.

🎓 Concepts You'll Learn

Trigonometric geometryObject-oriented design with classesClass compositionProcedural pattern generationSymmetry and rotationInteractive parameter controlStar polygon construction

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. It configures the canvas, creates DOM elements (sliders and labels), and calls updateSketch() to build the initial rosette. The noLoop() call is key: it makes the sketch render only on demand, not continuously, improving performance for a static geometric design.

function setup() {
  createCanvas(1000, 1000);
  angleMode(DEGREES);
  noFill();
  rectMode(CENTER);

  // Sliders
  nPointsLabel = createElement('label', 'Number of Points: ');
  nPointsLabel.position(10, 10);
  nPointsSlider = createSlider(8, 60, 24, 4);
  nPointsSlider.position(nPointsLabel.width + 15, 10);
  nPointsSlider.style('width', '200px');
  nPointsSlider.input(updateSketch);

  sqSideLengthLabel = createElement('label', 'Rosette Size: ');
  sqSideLengthLabel.position(10, 40);
  sqSideLengthSlider = createSlider(width / 16, width / 2, width / 4, 1);
  sqSideLengthSlider.position(sqSideLengthLabel.width + 15, 40);
  sqSideLengthSlider.style('width', '200px');
  sqSideLengthSlider.input(updateSketch);

  // Initial calculation and drawing
  updateSketch();
  noLoop();
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

calculation Canvas initialization createCanvas(1000, 1000);

Creates a 1000×1000 pixel canvas for drawing the rosette

setting Angle mode configuration angleMode(DEGREES);

Switches p5.js angle functions to use degrees instead of radians, making geometric calculations more intuitive

calculation Interactive sliders nPointsSlider = createSlider(8, 60, 24, 4);

Creates the first slider to control the number of rosette points from 8 to 60

setting Disable continuous drawing noLoop();

Stops the draw loop from running continuously; canvas only redraws when updateSketch() calls redraw()

createCanvas(1000, 1000);
Creates a square 1000×1000 pixel canvas where the rosette will be drawn
angleMode(DEGREES);
Tells p5.js to interpret all angle arguments (sin, cos, rotate) as degrees (0–360) instead of radians (0–2π), matching Islamic geometry conventions
noFill();
Disables fill for all shapes, so only outlines (strokes) will be visible
rectMode(CENTER);
Sets rectangle positioning mode to CENTER (though rectangles aren't used here, this line may be leftover from an earlier version)
nPointsLabel = createElement('label', 'Number of Points: ');
Creates a text label in the HTML DOM for the first slider
nPointsSlider = createSlider(8, 60, 24, 4);
Creates the first slider with minimum 8, maximum 60, default value 24, and step size 4
nPointsSlider.input(updateSketch);
Registers updateSketch() to be called whenever the user moves this slider
sqSideLengthSlider = createSlider(width / 16, width / 2, width / 4, 1);
Creates the second slider controlling rosette size: min ~62 pixels, max ~500, default ~250, step 1
updateSketch();
Calls updateSketch() once to initialize the rosette with default slider values
noLoop();
Disables the automatic 60-fps draw loop; instead, redraw() will only be called manually when updateSketch() triggers it

updateSketch()

updateSketch() is the callback function triggered whenever a slider moves. It reads both slider values, clears the old rosettes, and creates new Rosette objects with the updated parameters. By storing rosettes in an array, draw() can loop through and rotate each one the same way, creating the symmetric pattern.

function updateSketch() {
  let currentNPoints = nPointsSlider.value();
  let currentSqSideLength = sqSideLengthSlider.value();

  // Clear existing rosettes
  rosettes = [];

  // Create all rosette objects with current slider values
  for (let i = 0; i < currentNPoints; i++) {
    rosettes.push(new Rosette(currentSqSideLength, currentNPoints));
  }
  centerRosette = new Rosette(currentSqSideLength, currentNPoints); // Central rosette for stars
  redraw(); // Draw the canvas with updated parameters
}
Line-by-line explanation (7 lines)

🔧 Subcomponents:

calculation Read slider values let currentNPoints = nPointsSlider.value();

Captures the current position of the points slider to determine how many petals to create

for-loop Rosette creation loop for (let i = 0; i < currentNPoints; i++) { rosettes.push(new Rosette(currentSqSideLength, currentNPoints)); }

Creates currentNPoints Rosette objects and stores them in the array; each one is identical but will be rotated separately in draw()

calculation Central rosette creation centerRosette = new Rosette(currentSqSideLength, currentNPoints);

Creates a special Rosette object whose stars will be drawn at the center without rotation

let currentNPoints = nPointsSlider.value();
Reads the current slider value and stores it in a local variable so we know how many petals the user wants
let currentSqSideLength = sqSideLengthSlider.value();
Reads the current size slider value to determine the overall scale of the rosette
rosettes = [];
Clears the array of old Rosette objects so we can replace them with new ones based on the updated slider values
for (let i = 0; i < currentNPoints; i++) {
Loops from 0 up to currentNPoints (not including currentNPoints itself), so if currentNPoints is 24, the loop runs 24 times
rosettes.push(new Rosette(currentSqSideLength, currentNPoints));
Creates a new Rosette object with the current size and point count, and adds it to the rosettes array
centerRosette = new Rosette(currentSqSideLength, currentNPoints);
Creates one more Rosette with the same parameters; this one's stars will be drawn at the center of the canvas
redraw();
Manually triggers the draw() function once (since noLoop() is enabled), updating the canvas with the new rosettes

draw()

draw() runs once per redraw() call (not continuously, due to noLoop()). It translates to the center, then loops through all rosettes, rotating the coordinate system and drawing each petal. The combination of rotate() and push/pop ensures each petal is rotated independently, creating a symmetric pattern. Finally, displayStars() draws the ornamental center.

function draw() {
  // draw() is now only called when updateSketch() triggers redraw()
  // No need to check slider values here anymore!

  background(255);
  translate(width / 2, height / 2);

  let currentNPoints = nPointsSlider.value();
  let angle = 360 / currentNPoints;

  stroke(255, 0, 0); // Red stroke for petals
  strokeWeight(2);

  // 🌸 Draw rosettes
  for (let i = 0; i < currentNPoints; i++) {
    push();
    rotate(angle * i);
    let col = palette[i % palette.length]; // cycle through palette
    rosettes[i].display(col);
    pop();
  }

  // 🌟 Draw central star
  centerRosette.displayStars();
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

calculation Canvas clear background(255);

Fills the entire canvas with white, erasing the previous frame

transformation Coordinate translation translate(width / 2, height / 2);

Moves the origin (0, 0) to the center of the canvas, so rosettes can be drawn symmetrically around that point

for-loop Rosette drawing loop for (let i = 0; i < currentNPoints; i++) { push(); rotate(angle * i); let col = palette[i % palette.length]; rosettes[i].display(col); pop(); }

Loops through each rosette, rotates the coordinate system, and draws the petal—creating the symmetric ring effect

calculation Central star drawing centerRosette.displayStars();

Draws seven concentric star shapes at the center without rotation

background(255);
Fills the entire canvas with white (255, 255, 255), clearing any previous drawing
translate(width / 2, height / 2);
Moves the origin to the center of the canvas so that rotation and drawing are centered rather than starting from the top-left
let currentNPoints = nPointsSlider.value();
Reads the current number of points from the slider
let angle = 360 / currentNPoints;
Calculates the rotation angle between adjacent petals—for 24 points, this is 360÷24 = 15 degrees
stroke(255, 0, 0);
Sets the stroke color to red for drawing the petal outlines
for (let i = 0; i < currentNPoints; i++) {
Loops once for each petal that needs to be drawn
push();
Saves the current transformation state (rotation, translation) so the next rotation won't accumulate
rotate(angle * i);
Rotates the coordinate system by angle × i degrees, spacing the petals evenly around the center
let col = palette[i % palette.length];
Picks a color from the palette array using modulo to cycle back to the beginning if there are more petals than colors
rosettes[i].display(col);
Calls the display() method of the i-th rosette, drawing the petal shape at the rotated position
pop();
Restores the previous transformation state before the next loop iteration, preventing rotation from accumulating
centerRosette.displayStars();
Draws the seven concentric stars at the center of the rosette (at 0, 0 after translate)

safeDivide()

safeDivide() protects against division-by-zero errors by checking whether the denominator is effectively zero using an epsilon value (0.0001). This is important when calculating Rosette geometry with unusual point counts—some geometric formulas may produce zero denominators at edge cases, and safeDivide() ensures the sketch doesn't crash. The trigonometric geometry in the Rosette constructor uses this function heavily.

function safeDivide(numerator, denominator, defaultValue = 0) {
  // Use a small epsilon for floating point comparison to check if denominator is effectively zero
  return abs(denominator) < 0.0001 ? defaultValue : numerator / denominator;
}
Line-by-line explanation (2 lines)

🔧 Subcomponents:

conditional Near-zero denominator check return abs(denominator) < 0.0001 ? defaultValue : numerator / denominator;

Prevents division by zero by comparing the denominator's absolute value to a small threshold (epsilon)

function safeDivide(numerator, denominator, defaultValue = 0) {
Defines a helper function with three parameters: numerator and denominator (the division operands) and defaultValue (what to return if division would fail, defaulting to 0)
return abs(denominator) < 0.0001 ? defaultValue : numerator / denominator;
Uses a ternary operator: if the absolute value of denominator is less than 0.0001 (very close to zero), return defaultValue; otherwise, return numerator ÷ denominator

Rosette

The Rosette class is the heart of this sketch. Its constructor performs heavy trigonometric computation to derive all the geometric properties of a petal and the stars from just a size and point count. By pre-calculating and storing these values, the display() methods can render the geometry very efficiently. The class demonstrates the power of object-oriented design: all petal logic is encapsulated, making the main draw() function simple and clean.

🔬 These six points define the petal shape. The third point (index 2) is at the tip. What happens if you change this.sqSideLength * this.ratio to this.sqSideLength * this.ratio * 0.5? The petal will be shorter—why?

    this.petalPoints = [
      { x: this.xLength * this.cosHalfAngle * this.ratio, y: -this.xLength * this.sinHalfAngle * this.ratio },
      { x: this.xLength * this.cosHalfAngle + this.wLength, y: -this.xLength * this.sinHalfAngle * this.ratio },
      { x: this.sqSideLength * this.ratio, y: 0 },
      { x: this.xLength * this.cosHalfAngle + this.wLength * this.ratio, y: this.xLength * this.sinHalfAngle * this.ratio },
      { x: this.xLength * this.cosHalfAngle * this.ratio, y: this.xLength * this.sinHalfAngle * this.ratio },
      { x: this.dLength * this.ratio, y: 0 },
    ];
class Rosette {
  constructor(sqSideLength, nPoints, ratio = 1) {
    this.sqSideLength = sqSideLength;
    this.nPoints = nPoints;
    this.ratio = ratio;
    this.angle = 360 / nPoints;
    this.halfAngle = this.angle / 2;
    this.intAngle = ((this.nPoints - 2) * 180) / this.nPoints;
    this.halfIntAngle = this.intAngle / 2;
    this.twoAngle = 2 * this.angle;
    this.fourHalfAngle = 4 * this.halfAngle;
    this.threeHalfAngle = 3 * this.halfAngle;
    this.halfAnglePlus90 = 90 + this.halfAngle;
    this.triangleOneComplement = 180 - 2 * this.halfAngle;
    this.triangleTwoComplement = 180 - 3 * this.halfAngle;
    this.angleExtTriComplement = 180 - (this.halfIntAngle + this.halfAngle);

    this.sinHalfAngle = sin(this.halfAngle);
    this.cosHalfAngle = cos(this.halfAngle);
    this.tanHalfAngle = tan(this.halfAngle);
    this.sinAngle = sin(this.angle);
    this.sinAngleComplement = sin(180 - this.angle);
    this.sinAnglePlusHalfAngleComplement = sin(180 - (this.angle + this.halfAngle));
    this.sinTwoAngle = sin(this.twoAngle);
    this.sinHalfIntrAngle = sin(this.halfIntAngle);
    this.sinAngleExtTriComplement = sin(this.angleExtTriComplement);

    // Use safeDivide for all calculations that could potentially divide by zero
    this.rLength = safeDivide(this.sqSideLength * this.sinAngleExtTriComplement, this.sinTwoAngle);
    this.xLength = safeDivide(this.sinAngle * this.sqSideLength, this.sinAnglePlusHalfAngleComplement);
    this.dLength = safeDivide(this.sinHalfAngle * this.xLength, this.sinAngleComplement);
    this.tLength = safeDivide(this.sinAngle * this.dLength, this.sinAnglePlusHalfAngleComplement);
    this.yLength = this.sinHalfAngle * this.tanHalfAngle * this.xLength;
    this.zLength = this.sqSideLength - this.xLength * this.cosHalfAngle;
    this.wLength = this.zLength - this.yLength;

    this.petalPoints = [
      { x: this.xLength * this.cosHalfAngle * this.ratio, y: -this.xLength * this.sinHalfAngle * this.ratio },
      { x: this.xLength * this.cosHalfAngle + this.wLength, y: -this.xLength * this.sinHalfAngle * this.ratio },
      { x: this.sqSideLength * this.ratio, y: 0 },
      { x: this.xLength * this.cosHalfAngle + this.wLength * this.ratio, y: this.xLength * this.sinHalfAngle * this.ratio },
      { x: this.xLength * this.cosHalfAngle * this.ratio, y: this.xLength * this.sinHalfAngle * this.ratio },
      { x: this.dLength * this.ratio, y: 0 },
    ];

    // Pre-instantiate all Star objects in the constructor
    // Apply safeDivide to star radius calculations as well
    this.stars = [
      new Star(
        0,
        0,
        safeDivide(this.sqSideLength * sin(90 + this.halfAngle), sin(90 - 2 * this.halfAngle)),
        this.sqSideLength,
        this.nPoints
      ),
      new Star(
        0,
        0,
        safeDivide(this.sqSideLength * sin(90 + this.halfAngle), sin(90 - 2 * this.halfAngle)),
        safeDivide(this.sqSideLength * sin(90 + this.halfAngle), sin(90 - 3 * this.halfAngle)),
        this.nPoints
      ),
      new Star(
        0,
        0,
        safeDivide(this.sqSideLength * sin(90 + this.halfAngle), sin(90 - 4 * this.halfAngle)),
        safeDivide(this.sqSideLength * sin(90 + this.halfAngle), sin(90 - 3 * this.halfAngle)),
        this.nPoints
      ),
      new Star(
        0,
        0,
        safeDivide(this.sqSideLength * sin(90 + this.halfAngle), sin(90 - 4 * this.halfAngle)),
        safeDivide(this.sqSideLength * sin(90 + this.halfAngle), sin(90 - 5 * this.halfAngle)),
        this.nPoints
      ),
      new Star(
        0,
        0,
        safeDivide(this.sqSideLength * sin(90 + this.halfAngle), sin(90 - 6 * this.halfAngle)),
        safeDivide(this.sqSideLength * sin(90 + this.halfAngle), sin(90 - 5 * this.halfAngle)),
        this.nPoints
      ),
      new Star(0, 0, this.tLength, this.dLength, this.nPoints),
      new Star(0, 0, this.xLength, this.dLength, this.nPoints),
    ];
  }

  display(col) {
    stroke('black'); // Use the passed color for the petal outline
    strokeWeight(2);
    noFill();
    beginShape();
    for (let pt of this.petalPoints) {
      vertex(pt.x, pt.y);
    }
    endShape(CLOSE);
  }

  displayStars(x = 0, y = 0) {
    // Iterate through the pre-instantiated Star objects and display them
    stroke(255, 0, 255); // Magenta for outer stars
    this.stars[0].display();
    stroke(0, 255, 0); // Green for middle outer stars
    this.stars[1].display();
    stroke(255, 0, 255); // Magenta for inner outer stars
    this.stars[2].display();
    stroke(0, 255, 0); // Green for fourth outer stars
    this.stars[3].display();
    stroke(0, 0, 255); // Blue for fifth outer stars
    this.stars[4].display();
    stroke(139, 0, 0); // Dark red for first inner star
    this.stars[5].display();
    stroke(255, 0, 0); // Red for second inner star
    this.stars[6].display();
  }
}
Line-by-line explanation (17 lines)

🔧 Subcomponents:

calculation Angle calculations this.angle = 360 / nPoints;

Computes the central angle between adjacent petals and derives all related angles (half-angle, interior angle, etc.)

calculation Trigonometric precomputation this.sinHalfAngle = sin(this.halfAngle);

Pre-calculates sine, cosine, and tangent values for various angles to avoid recalculating them repeatedly

calculation Petal dimension calculations this.xLength = safeDivide(this.sinAngle * this.sqSideLength, this.sinAnglePlusHalfAngleComplement);

Calculates geometric lengths (xLength, yLength, etc.) that define the petal's shape using trigonometry

calculation Petal vertex definition this.petalPoints = [ ... ];

Creates an array of six points defining the corners of the petal polygon

calculation Star object instantiation this.stars = [ new Star(...), ... ];

Pre-creates seven Star objects with calculated radii for concentric star display

class Rosette {
Declares the Rosette class, which encapsulates all data and methods for a single petal and its associated stars
constructor(sqSideLength, nPoints, ratio = 1) {
The constructor receives the rosette size (sqSideLength), point count (nPoints), and an optional ratio parameter (defaults to 1)
this.sqSideLength = sqSideLength;
Stores the petal size parameter as an instance variable
this.angle = 360 / nPoints;
Computes the central angle between adjacent petals—for 24 points, this is 15 degrees
this.halfAngle = this.angle / 2;
Calculates half the central angle, used frequently in petal geometry
this.intAngle = ((this.nPoints - 2) * 180) / this.nPoints;
Calculates the interior angle of a regular polygon with nPoints sides
this.sinHalfAngle = sin(this.halfAngle);
Pre-calculates the sine of the half-angle and stores it to avoid recalculating during display
this.xLength = safeDivide(this.sinAngle * this.sqSideLength, this.sinAnglePlusHalfAngleComplement);
Uses trigonometry to compute one key dimension of the petal; safeDivide prevents crashes if the denominator is near zero
this.petalPoints = [ { x: ..., y: ... }, ... ];
Defines six vertices (as objects with x and y properties) that form the petal's outline
this.stars = [ new Star(0, 0, rInner, rOuter, nPoints), ... ];
Pre-creates seven Star objects, each with a specific pair of inner and outer radii, all centered at (0, 0)
stroke('black');
Sets the outline color to black for drawing the petal
beginShape();
Starts defining a custom polygon; subsequent vertex() calls add points to it
for (let pt of this.petalPoints) { vertex(pt.x, pt.y); }
Loops through petalPoints and adds each as a vertex, building the petal polygon
endShape(CLOSE);
Closes the polygon by connecting the last vertex back to the first, and renders it with the current stroke
displayStars(x = 0, y = 0) {
Defines the displayStars method; x and y parameters are provided for flexibility but default to 0, 0 (the center)
stroke(255, 0, 255);
Sets the stroke color to magenta before drawing the first star
this.stars[0].display();
Calls the display() method of the first pre-created Star, drawing it in the current stroke color

Star

The Star class draws a star polygon by alternating between outer and inner radii as it loops through angles. The getSideLength() method (currently unused) computes the distance from an outer point to its adjacent inner point—it's a utility that could be useful for future enhancements. The display() method is called seven times per rosette, with different colors, layering stars to create the ornamental center.

🔬 This loop alternates between outer (rOuter) and inner (rInner) vertices. What happens if you change a + halfAngle to a + angle so both vertices are at the full angle instead of offset? The star will look very different—why?

    for (let a = 0; a < 360; a += angle) {
      let sx = this.x + cos(a) * this.rOuter;
      let sy = this.y + sin(a) * this.rOuter;
      vertex(sx, sy);
      sx = this.x + cos(a + halfAngle) * this.rInner;
      sy = this.y + sin(a + halfAngle) * this.rInner;
      vertex(sx, sy);
    }
class Star {
  constructor(x, y, rInner, rOuter, npoints) {
    this.x = x;
    this.y = y;
    this.rInner = rInner;
    this.rOuter = rOuter;
    this.npoints = npoints;
  }

  // ⭐ NEW: compute the side length of the star
  getSideLength() {
    let angle = 360 / this.npoints;
    let halfAngle = angle / 2;

    // First outer vertex
    let x1 = this.x + cos(0) * this.rOuter;
    let y1 = this.y + sin(0) * this.rOuter;

    // Next inner vertex
    let x2 = this.x + cos(halfAngle) * this.rInner;
    let y2 = this.y + sin(halfAngle) * this.rInner;

    return dist(x1, y1, x2, y2);
  }

  display() {
    let angle = 360 / this.npoints;
    let halfAngle = angle / 2;
    beginShape();
    for (let a = 0; a < 360; a += angle) {
      let sx = this.x + cos(a) * this.rOuter;
      let sy = this.y + sin(a) * this.rOuter;
      vertex(sx, sy);
      sx = this.x + cos(a + halfAngle) * this.rInner;
      sy = this.y + sin(a + halfAngle) * this.rInner;
      vertex(sx, sy);
    }
    endShape(CLOSE);
  }
}
Line-by-line explanation (16 lines)

🔧 Subcomponents:

calculation Star initialization constructor(x, y, rInner, rOuter, npoints) {

Stores the star's position, inner radius, outer radius, and point count

calculation Side length computation return dist(x1, y1, x2, y2);

Calculates the distance between an outer point and the adjacent inner point

for-loop Star vertex generation loop for (let a = 0; a < 360; a += angle) {

Loops through each angle interval, alternating between outer and inner vertices to create the star shape

class Star {
Declares the Star class, which defines a star polygon with adjustable inner and outer radii
constructor(x, y, rInner, rOuter, npoints) {
Creates a Star at position (x, y) with inner radius rInner, outer radius rOuter, and npoints points
this.x = x; this.y = y;
Stores the star's center coordinates
this.rInner = rInner; this.rOuter = rOuter;
Stores the radii defining the star's inner and outer vertices
let angle = 360 / this.npoints;
Calculates the angular spacing between adjacent points of the star
let halfAngle = angle / 2;
Computes half the angle, used to alternate between outer and inner vertices
let x1 = this.x + cos(0) * this.rOuter;
Calculates the x-coordinate of the first outer vertex (at angle 0 degrees from the center)
let x2 = this.x + cos(halfAngle) * this.rInner;
Calculates the x-coordinate of the first inner vertex (at angle halfAngle from the center)
return dist(x1, y1, x2, y2);
Uses the dist() function to compute the straight-line distance between the two points
beginShape();
Starts defining the star polygon
for (let a = 0; a < 360; a += angle) {
Loops through angles from 0 to 360 degrees in steps of the angle between points
let sx = this.x + cos(a) * this.rOuter;
Computes the x-coordinate of an outer vertex at the current angle
vertex(sx, sy);
Adds the outer vertex to the polygon
sx = this.x + cos(a + halfAngle) * this.rInner;
Computes the x-coordinate of the next inner vertex (offset by halfAngle)
vertex(sx, sy);
Adds the inner vertex to the polygon
endShape(CLOSE);
Closes and renders the star polygon

📦 Key Variables

palette array

Stores five color hex codes used to color the petals—one per point in the palette cycles through as petals are drawn

let palette = ["#e63946", "#f1faee", "#a8dadc", "#457b9d", "#1d3557"];
rosettes array

Holds all Rosette objects created for the current point count; each element is one petal (though all identical)

let rosettes = [];
centerRosette object (Rosette)

A special Rosette object whose displayStars() method is called at the center to render the ornamental stars

let centerRosette;
nPointsSlider p5.Renderer (HTMLElement)

The slider DOM element that lets users control the number of rosette points (8–60)

let nPointsSlider;
sqSideLengthSlider p5.Renderer (HTMLElement)

The slider DOM element that lets users control the rosette size (125–500 pixels)

let sqSideLengthSlider;
nPointsLabel p5.Renderer (HTMLElement)

A text label displayed in the HTML DOM next to the points slider

let nPointsLabel;
sqSideLengthLabel p5.Renderer (HTMLElement)

A text label displayed in the HTML DOM next to the size slider

let sqSideLengthLabel;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

BUG display() method in Rosette class

The display() method receives a 'col' parameter but never uses it; instead, it always draws with stroke('black')

💡 Change stroke('black') to stroke(col) so each petal can be colored according to the palette passed from draw()

PERFORMANCE Rosette constructor

Pre-calculates many trigonometric values (sinHalfAngle, cosHalfAngle, etc.) that are used only once or twice, increasing memory without proportional benefit

💡 Keep only the most-used trig values (like sinHalfAngle and cosHalfAngle); compute others inline or in utility functions to save memory

STYLE safeDivide() function

The epsilon value (0.0001) is hard-coded; changing it requires modifying the function, making it inflexible for different use cases

💡 Make epsilon a parameter with a sensible default: function safeDivide(numerator, denominator, defaultValue = 0, epsilon = 0.0001)

FEATURE Star class

The getSideLength() method is defined but never called; it could be useful for dynamic geometry but is currently unused

💡 Either remove getSideLength() if it's not needed, or use it to compute star sizing dynamically based on the rosette geometry

🔄 Code Flow

Code flow showing setup, updatesketch, draw, safedivide, rosette, star

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> canvas-setup[canvas-setup] setup --> angle-mode[angle-mode] setup --> slider-creation[slider-creation] setup --> no-loop[no-loop] setup --> updatesketch[updatesketch] click setup href "#fn-setup" click canvas-setup href "#sub-canvas-setup" click angle-mode href "#sub-angle-mode" click slider-creation href "#sub-slider-creation" click no-loop href "#sub-no-loop" click updatesketch href "#fn-updatesketch" updatesketch --> slider-read[slider-read] updatesketch --> background-clear[background-clear] updatesketch --> rosette-loop[rosette-loop] updatesketch --> central-rosette[central-rosette] click slider-read href "#sub-slider-read" click background-clear href "#sub-background-clear" click rosette-loop href "#sub-rosette-loop" click central-rosette href "#sub-central-rosette" rosette-loop --> rosette-loop[rosette-loop] click rosette-loop href "#sub-rosette-loop" draw[draw] --> translate-center[translate-center] draw --> rosette-loop[rosette-loop] draw --> star-display[star-display] click draw href "#fn-draw" click translate-center href "#sub-translate-center" click rosette-loop href "#sub-rosette-loop" click star-display href "#sub-star-display" rosette-loop --> background-clear[background-clear] rosette-loop --> rosette-loop[rosette-loop] click background-clear href "#sub-background-clear" click rosette-loop href "#sub-rosette-loop" star-display --> star-creation[star-creation] star-creation --> constructor[constructor] star-creation --> display-loop[display-loop] click star-creation href "#sub-star-creation" click constructor href "#sub-constructor" click display-loop href "#sub-display-loop" safedivide[safedivide] --> epsilon-check[epsilon-check] epsilon-check --> angle-calcs[angle-calcs] angle-calcs --> trig-precompute[trig-precompute] trig-precompute --> length-calcs[length-calcs] length-calcs --> petal-points[petal-points] click safedivide href "#fn-safedivide" click epsilon-check href "#sub-epsilon-check" click angle-calcs href "#sub-angle-calcs" click trig-precompute href "#sub-trig-precompute" click length-calcs href "#sub-length-calcs" click petal-points href "#sub-petal-points" star --> getSideLength[getSideLength] click star href "#fn-star" click getSideLength href "#sub-getSideLength"

❓ Frequently Asked Questions

What visual elements does the p5.js sketch create?

The sketch generates intricate Islamic rosette motifs using a customizable color palette and geometric patterns.

How can users modify the sketch's appearance?

Users can interact with the sketch by adjusting sliders for the number of points on the rosettes and their size, allowing for dynamic visual changes.

What creative coding concepts are showcased in this p5.js sketch?

The sketch demonstrates the use of parametric design and generative art principles, utilizing interactive elements to create complex patterns.

Preview

Sketch 2026-06-08 16:23 (Remix) (Remix) (Remix) - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Sketch 2026-06-08 16:23 (Remix) (Remix) (Remix) - Code flow showing setup, updatesketch, draw, safedivide, rosette, star
Code Flow Diagram