Morphing String using Geomerative library, bug on the iterations of the program

Hi,
I wrote a program that uses Geomerative library in order to morph between different words that ends with “ire” and form a short poem, but when the program loops instead of repeating perfectly the animation, it bugs!?
Instead of the first word “fire me” morphing immediatly to form the second “hire me” its stays still ?! How can I repeat perfectly the first loop please? I don’t find the bug if you can help me please. Sorry for the long code below, but I think you need it all in order to debug! Thanks a lot!!

import geomerative.*;
import processing.sound.*;

SoundFile[] file;

Clock[][] clocks;
String[] words = {
  "fire me", "hire me", "desire me", "disattire me",
  "inspire me", "exquire me", "admire me", "dewire me"
};
String[][] tokens;
RFont text;
RShape[][] shapes;
int splitGlyph = 500;
int inc = 200;

RPoint[][][] points;
float[][][] shiftPntX;
float[][][] shiftPntY;

float timeFactor = 6000;
float morphDuration;
PGraphics pg;

int morph = 0;
boolean run = false;
boolean forward = true;
float diam = 0;
int currentMorph = 0;
long pauseStartTime = 0;
boolean isPaused = false;
long morphStartTime = 0;

// Store positions for the fixed phrases
float[][][] posStaticX;
float[][][] posStaticY;
boolean[] showStatic;
float[] staticAlpha;
boolean fadingOut = false;

void setup() {
  fullScreen();
  smooth(4);

  tokens = new String[words.length][];
  shapes = new RShape[words.length][];
  points = new RPoint[words.length][][];

  for (int i = 0; i < words.length; i++) {
    tokens[i] = splitTokens(words[i]);
    shapes[i] = new RShape[tokens[i].length];
    points[i] = new RPoint[tokens[i].length][splitGlyph];
  }

  clocks = new Clock[tokens[0].length][splitGlyph];
  shiftPntX = new float[tokens[0].length][inc * words.length][splitGlyph];
  shiftPntY = new float[tokens[0].length][inc * words.length][splitGlyph];

  posStaticX = new float[words.length][tokens[0].length][splitGlyph];
  posStaticY = new float[words.length][tokens[0].length][splitGlyph];
  showStatic = new boolean[words.length];
  staticAlpha = new float[words.length];

  RG.init(this);
  text = new RFont("FreeSans.ttf", 150, RIGHT);

  float frac = 1.0 / splitGlyph;

  for (int w = 0; w < words.length; w++) {
    for (int i = 0; i < tokens[w].length; i++) {
      shapes[w][i] = text.toShape(tokens[w][i]);
      for (int j = 0; j < splitGlyph; j++) {
        points[w][i][j] = shapes[w][i].getPoint(j * frac);
        if (w == 0) {
          float offsetMinutes = 7.5;
          float offsetHours = 24;
          float startHours = offsetHours * j * 1 / 8;
          float startMinutes = j * offsetMinutes;
          clocks[i][j] = new Clock(points[w][i][j].x, points[w][i][j].y, startHours, startMinutes, color(200, 200, 0, 10));
        }
      }
    }
  }

  for (int i = 0; i < tokens[0].length; i++) {
    for (int k = 0; k < inc; k++) {
      for (int j = 0; j < splitGlyph; j++) {
        for (int w = 0; w < words.length - 1; w++) {
          if (points[w][i][j] != null && points[w + 1][i][j] != null) {
            shiftPntX[i][k + w * inc][j] = lerp(points[w][i][j].x, points[w + 1][i][j].x, k * (1.0 / inc));
            shiftPntY[i][k + w * inc][j] = lerp(points[w][i][j].y, points[w + 1][i][j].y, k * (1.0 / inc));
          }
        }
        if (points[words.length - 1][i][j] != null && points[0][i][j] != null) {
          shiftPntX[i][k + (words.length - 1) * inc][j] = lerp(points[words.length - 1][i][j].x, points[0][i][j].x, k * (1.0 / inc));
          shiftPntY[i][k + (words.length - 1) * inc][j] = lerp(points[words.length - 1][i][j].y, points[0][i][j].y, k * (1.0 / inc));
        }
      }
    }
  }

  morph = 0;
  morphStartTime = millis();

  // Calculate morph duration to ensure equal timing
  morphDuration = (float) (12000.0 / words.length);

  file = new SoundFile[5];
  for (int i = 0; i < 5; i++) {
    file[i] = new SoundFile(this, "Essais_piano_01.wav");
    file[i].jump(i * file[i].duration() / 25);
    file[i].amp(0.0001);
    file[i].play();
  }
}

void draw() {
  background(0);
  translate(width / 2, 150);

  float lineHeight = 130; // Height of each line
  float offsetX = 0;
  float firstWordWidth = 0;

  // Calculate morphIndex safely
  int morphIndex = currentMorph * inc + morph;
  morphIndex = min(morphIndex, inc * words.length - 1);

  // Compute the width of the first word for alignment
  for (int j = 0; j < splitGlyph; j++) {
    firstWordWidth = max(firstWordWidth, shiftPntX[0][morphIndex][j]);
  }
  float fixedOffset = firstWordWidth + 220;

  // Morphing and updating clocks
  for (int i = 0; i < tokens[0].length; i++) {
    for (int j = 0; j < splitGlyph; j++) {
      pushStyle();

      if (morphIndex < inc * words.length) {
        float yOffset = currentMorph * lineHeight + (lineHeight * morph / (float) inc);
        clocks[i][j].setPosition(shiftPntX[i][morphIndex][j] + offsetX, shiftPntY[i][morphIndex][j] + yOffset);
        clocks[i][j].update();
        clocks[i][j].display();
      }
      popStyle();
    }

    if (i == 0) {
      offsetX += fixedOffset;
    }
  }

  // Handle pauses and transitions
  if (isPaused) {
    if (!fadingOut && millis() - morphStartTime >= morphDuration) {
      isPaused = false;
      currentMorph = (currentMorph + 1) % words.length;
      morph = 0; // Reset morph to start the next transition
      morphStartTime = millis();

      // Ensure the cycle restarts correctly
      if (currentMorph == 0) {
        fadingOut = true; // Start fading out when transitioning from last to first phrase
      }
    }

    // Update positions for static phrases
    for (int i = 0; i < tokens[0].length; i++) {
      for (int j = 0; j < splitGlyph; j++) {
        if (currentMorph > 0) {
          posStaticX[currentMorph][i][j] = shiftPntX[i][currentMorph * inc + inc - 1][j];
          posStaticY[currentMorph][i][j] = shiftPntY[i][currentMorph * inc + inc - 1][j];
        } else {
          posStaticX[currentMorph][i][j] = shiftPntX[i][(words.length - 1) * inc + inc - 1][j];
          posStaticY[currentMorph][i][j] = shiftPntY[i][(words.length - 1) * inc + inc - 1][j];
        }
      }
    }
  } else {
    morph = (int) ((millis() - morphStartTime) / morphDuration * inc);
    if (morph >= inc) {
      isPaused = true;
      pauseStartTime = millis();

      // Store positions for the current static phrase
      for (int i = 0; i < tokens[0].length; i++) {
        for (int j = 0; j < splitGlyph; j++) {
          posStaticX[currentMorph][i][j] = shiftPntX[i][morphIndex][j];
          posStaticY[currentMorph][i][j] = shiftPntY[i][morphIndex][j];
        }
      }
      showStatic[currentMorph] = true;

      // Check if we are transitioning from "dewire me" to "fire me"
      if (currentMorph == words.length - 1) {
        fadingOut = true;
      }
    }
  }

  // Draw static phrases with fading effects
  for (int w = 0; w < words.length; w++) {
    if (showStatic[w]) {
      for (int i = 0; i < tokens[0].length; i++) {
        for (int j = 0; j < splitGlyph; j++) {
          pushStyle();
          fill(255, staticAlpha[w]);
          clocks[i][j].setPosition(posStaticX[w][i][j] + i * offsetX, posStaticY[w][i][j] + 130 * w + 130);
          clocks[i][j].update();
          clocks[i][j].display();
          popStyle();
        }
      }
    }
  }

  // Global fade-out logic when transitioning from last to first phrase
  if (fadingOut) {
    boolean allFaded = true;
    for (int w = 0; w < words.length; w++) {
      staticAlpha[w] = max(0, staticAlpha[w] - 10);
      showStatic[w] = staticAlpha[w] > 0;
      if (staticAlpha[w] > 0) {
        allFaded = false;
      }
    }

    if (allFaded) {
      // Once all phrases are faded out, reset everything for the next loop
      fadingOut = false;
      for (int w = 0; w < words.length; w++) {
        staticAlpha[w] = 255;
        showStatic[w] = false;
      }
      morph = 0;
      currentMorph = 0;
      morphStartTime = millis();
    }
  }

  // Ensure sounds play correctly
  for (int i = 0; i < 5; i++) {
    if (!file[i].isPlaying()) {
      file[i].jump(i * file[i].duration() / 25);
      file[i].amp(0.0001);
      file[i].play();
    }
  }
}

void mousePressed() {
  run = !run;
}

class Clock {
  float cx, cy;
  float radius;
  float clockDiameter;
  float hourRadius;
  float minuteRadius;
  float startHours;
  float startMinutes;
  float h, m;
  color col;
  long startMillis;
  float currentMinutes;

  Clock(float _cx, float _cy, float _startHours, float _startMinutes, color _col) {
    radius = random(10, 25);
    minuteRadius = radius * 0.3;
    hourRadius = radius * 0.15;
    clockDiameter = radius * 1;
    col = _col;
    cx = _cx;
    cy = _cy;
    startHours = _startHours;
    startMinutes = _startMinutes;
    startMillis = millis();
  }

  void setPosition(float _cx, float _cy) {
    cx = _cx;
    cy = _cy;
  }

  void setTime(float _startHours, float _startMinutes) {
    startHours = _startHours;
    startMinutes = _startMinutes;
  }

  void update() {
    long elapsedMillis = millis() - startMillis;
    float elapsedSeconds = (elapsedMillis / 1000.0) * timeFactor;
    float elapsedMinutes = elapsedSeconds / 60.0;
    float elapsedHours = elapsedSeconds / 3600.0;

    float currentHours = startHours + elapsedHours;
    float currentMinutes = startMinutes + elapsedMinutes;

    currentHours = currentHours % 24;
    currentMinutes = currentMinutes % 60;

    h = map(currentHours, 0, 12, 0, TWO_PI) - HALF_PI;
    m = map(currentMinutes, 0, 60, 0, TWO_PI) - HALF_PI;
  }

  void display() {
    col = color(250, 200, 0);
    noStroke();
    fill(col, map(m, 0, TWO_PI, 50, 255));

    arc(cx, cy, minuteRadius * 2, minuteRadius * 2, -HALF_PI, m);

    stroke(255, 150);
    strokeWeight(1.5);

    line(cx, cy, cx + cos(h) * hourRadius, cy + sin(h) * hourRadius);
    strokeWeight(1);
    line(cx, cy, cx + cos(m) * minuteRadius, cy + sin(m) * minuteRadius);
  }
}
1 Like

In my version, “fire me” disappears and all other stay.

Does it have to do with showStatic[w] ?

NICE Sketch!

2 Likes

Yes sorry “fire me” is absent in this version and the Strings swing also on the x axis. Tha fact that the second loop is not the same as the first one certainly has to do with showStatic[w] and all the other variables : morph, morphIndex, words that need to be reset at the end of the program just before the loop when “dewire me” is morphing to turn into “fire me”… Need to check it out again. Thanks a lot for your compliment ;-))

Dear Chrisir,
I am sorry for this dirty sketch, but it’s all what I managed to do…! At least it works! I am sure it’s possible to refine and simplify the sketch, but not sure I am able to handle it. At the end I wrote a method to loop in a clean way, now it shows “fire me” all the way long, and I play a little bit with the sound… Have a god day!

import geomerative.*;
import processing.sound.*;

SoundFile[] file;

Clock[][] clocks;
String[] words = {
  "fire me", "hire me", "desire me", "disattire me",
  "inspire me", "exquire me", "admire me", "dewire me"
};
String[][] tokens;
RFont text;
RShape[][] shapes;
int splitGlyph = 500;
int inc = 200;

RPoint[][][] points;
float[][][] shiftPntX;
float[][][] shiftPntY;

float timeFactor = 6000;
float morphDuration;
PGraphics pg;

int morph = 0;
boolean run = false;
boolean forward = true;
float diam = 0;
int currentMorph = 0;
long pauseStartTime = 0;
boolean isPaused = false;
long morphStartTime = 0;

// Store positions for the fixed phrases
float[][][] posStaticX;
float[][][] posStaticY;
boolean[] showStatic;
float[] staticAlpha;
boolean fadingOut = false;
float frac = 1.0 / splitGlyph;

// Static "fire me" phrase
RShape staticFireMe;
RPoint[] staticFireMePoints;
boolean shiftPitch = false;

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void setup() {
  //size(1500, 1200);
  fullScreen();
  smooth(4);

  tokens = new String[words.length][];
  shapes = new RShape[words.length][];
  points = new RPoint[words.length][][];

  for (int i = 0; i < words.length; i++) {
    tokens[i] = splitTokens(words[i]);
    shapes[i] = new RShape[tokens[i].length];
    points[i] = new RPoint[tokens[i].length][splitGlyph];
  }

  clocks = new Clock[tokens[0].length][splitGlyph];
  shiftPntX = new float[tokens[0].length][inc * words.length][splitGlyph];
  shiftPntY = new float[tokens[0].length][inc * words.length][splitGlyph];

  posStaticX = new float[words.length][tokens[0].length][splitGlyph];
  posStaticY = new float[words.length][tokens[0].length][splitGlyph];
  showStatic = new boolean[words.length];
  staticAlpha = new float[words.length];

  RG.init(this);
  text = new RFont("FreeSans.ttf", 150, RIGHT);



  for (int w = 0; w < words.length; w++) {
    for (int i = 0; i < tokens[w].length; i++) {
      shapes[w][i] = text.toShape(tokens[w][i]);
      for (int j = 0; j < splitGlyph; j++) {
        points[w][i][j] = shapes[w][i].getPoint(j * frac);
        float offsetMinutes = 7.5;
        float offsetHours = 24;
        float startHours = offsetHours * j * 1 / 8;
        float startMinutes = j * offsetMinutes;
        clocks[i][j] = new Clock(points[w][i][j].x, points[w][i][j].y, startHours, startMinutes, color(200, 200, 0, 10));
      }
    }
  }

  for (int i = 0; i < tokens[0].length; i++) {
    for (int k = 0; k < inc; k++) {
      for (int j = 0; j < splitGlyph; j++) {
        for (int w = 0; w < words.length - 1; w++) {
          if (points[w][i][j] != null && points[w+1][i][j] != null) {
            shiftPntX[i][k + w * inc][j] = lerp(points[w][i][j].x, points[w + 1][i][j].x, k * (1.0 / inc));
            shiftPntY[i][k + w * inc][j] = lerp(points[w][i][j].y, points[w + 1][i][j].y, k * (1.0 / inc));
          }
        }
        if (points[words.length - 1][i][j] != null && points[0][i][j] != null) {
          shiftPntX[i][k + (words.length - 1) * inc][j] = lerp(points[words.length - 1][i][j].x, points[0][i][j].x, k * (1.0 / inc));
          shiftPntY[i][k + (words.length - 1) * inc][j] = lerp(points[words.length - 1][i][j].y, points[0][i][j].y, k * (1.0 / inc));
        }
      }
    }
  }

  morph = 0;
  morphStartTime = millis();

  // Calculate morph duration to ensure equal timing
  morphDuration = (float) (12000.0 / words.length);

  // Initialize static "fire me" phrase
  staticFireMe = text.toShape("fire me");
  staticFireMePoints = new RPoint[splitGlyph];
  for (int j = 0; j < splitGlyph; j++) {
    staticFireMePoints[j] = staticFireMe.getPoint(j * frac);
  }
  file = new SoundFile[5];
  for (int i = 0; i < 5; i++) {
    file[i] = new SoundFile(this, "Essais_piano_01.wav");
    file[i].amp(0.0001);
    //file[i].loop();
    file[i].jump(i * file[i].duration() / 25);
  }
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void draw() {
  background(0);
  translate(width / 2, 150);

  float lineHeight = 130; // Height of each line
  float offsetX = 0;
  float firstWordWidth = 0;

  // Calculate morphIndex safely
  int morphIndex = currentMorph * inc + morph;
  morphIndex = min(morphIndex, inc * words.length - 1);

  // Compute the width of the first word for alignment
  for (int j = 0; j < splitGlyph; j++) {
    firstWordWidth = max(firstWordWidth, shiftPntX[0][morphIndex][j]);
  }
  float fixedOffset = 240;

  // Morphing and updating clocks
  for (int i = 0; i < tokens[0].length; i++) {
    for (int j = 0; j < splitGlyph; j++) {
      pushStyle();

      if (morphIndex < inc * words.length) {
        float yOffset = currentMorph * lineHeight + (lineHeight * morph / (float) inc);
        clocks[i][j].setPosition(shiftPntX[i][morphIndex][j] + offsetX, shiftPntY[i][morphIndex][j] + yOffset);
        clocks[i][j].update();
        clocks[i][j].display();
      }
      popStyle();
    }
    if (i == 0) {
      offsetX += fixedOffset;
    }
  }
  for (int i = 0; i < 5; i++) {
    if (shiftPitch==false) {
      // Sound beahvior during morph
      float morphProgress = morph /(float)inc; //range [0.0, 1.0]
      float pitch1 = map(morphProgress, 0, 1, 0.995, 1.015);
      file[i].rate(pitch1);
      shiftPitch = false;
    } else {
      float morphProgress = morph /(float)inc; //range [0.0, 1.0]
      float pitch1 = map(morphProgress, 0, 1, 0.5, 1);
      file[i].rate(pitch1);
      shiftPitch = true;
      
    }
  }
  // Handle pauses and transitions
  if (isPaused) {
    if (millis() - pauseStartTime > 50) {
      isPaused = false;
      currentMorph = (currentMorph +1) % words.length;
      morph = 0; // Reset morph to start the next transition
      morphStartTime = millis();
    }

    // Update positions for static phrases
    for (int i = 0; i < tokens[0].length; i++) {
      for (int j = 0; j < splitGlyph; j++) {
        if (currentMorph > 0) {
          posStaticX[currentMorph][i][j] = shiftPntX[i][(currentMorph ) * inc + inc - 1][j];
          posStaticY[currentMorph][i][j] = shiftPntY[i][(currentMorph ) * inc + inc - 1][j];
        } else {
          posStaticX[0][i][j] = shiftPntX[i][(currentMorph ) * inc + inc - 1][j];
          posStaticY[0][i][j] = shiftPntY[i][(currentMorph ) * inc + inc - 1][j];
        }
      }
    }
  } else {
    morph = (int) ((millis() - morphStartTime) / morphDuration * inc);
    if (morph >= inc) {
      isPaused = true;
      pauseStartTime = millis();

      // Store positions for the current static phrase
      for (int i = 0; i < tokens[0].length; i++) {

        for (int j = 0; j < splitGlyph; j++) {
          posStaticX[currentMorph][i][j] = shiftPntX[i][morphIndex][j];
          posStaticY[currentMorph][i][j] = shiftPntY[i][morphIndex][j];
        }
      }
      showStatic[currentMorph] = true;

      // Check if we are transitioning from "dewire me" to "fire me"
      if (currentMorph == words.length - 1) {
        fadingOut = true;
      }
    }
  }
  // Draw static phrases with fading effects
  for (int w = 0; w < words.length; w++) {
    if (showStatic[w]) {
      for (int i = 0; i < tokens[0].length; i++) {
        for (int j = 0; j < splitGlyph; j++) {
          pushStyle();
          fill(255, staticAlpha[w]);
          clocks[i][j].setPosition(posStaticX[w][i][j] + i * offsetX, posStaticY[w][i][j] + 130 * w + 130);
          clocks[i][j].update();
          clocks[i][j].display();
          popStyle();
        }
      }
    }
  }

  // Draw the static "fire me" phrase at the beginning
  if (!fadingOut) {
    pushStyle();
    fill(255);
    for (int j = 0; j < splitGlyph; j++) {
      clocks[0][j].setPosition(staticFireMePoints[j].x+250, staticFireMePoints[j].y);
      clocks[0][j].update();
      clocks[0][j].display();
    }
    popStyle();
  }

  // Global fade-out logic when transitioning from last to first phrase
  if (fadingOut) {
    boolean allFaded = true;
    for (int w = 0; w < words.length; w++) {
      staticAlpha[w] = max(0, staticAlpha[w] - 10);
      if (staticAlpha[w] >= 0 && staticAlpha[w] < words.length-1) {
        allFaded = false;
      }
    }

    resetProgram();
    if (allFaded) {
      // Once all phrases are faded out, reset everything for the next loop
      fadingOut = false;
      for (int w = 0; w < words.length; w++) {
        staticAlpha[w] = 255;
        showStatic[w] = false;
      }
      morph = 0;
      currentMorph = 0;
      morphStartTime = millis();
      pauseStartTime = 0;
    }
  }
  // Ensure sounds play correctly
  for (int i = 0; i < 5; i++) {
    if (!file[i].isPlaying()) {
      float morphProgress = morph /(float)inc; //range [0.0, 1.0]
      float pitch = map(morphProgress, 0, 1, 0.995, 1.5);
      file[i].jump(i * file[i].duration() / 25);
      file[i].rate(pitch);
      file[i].amp(0.0000001);
      file[i].play();
    }
  }
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Reset function to set all variables to their initial state
void resetProgram() {
  morph = 0;
  currentMorph = 0;
  pauseStartTime = 0;
  isPaused = false;
  fadingOut = false;
  forward = true;
  morphStartTime = millis();
  splitGlyph = 500;
  inc = 200;

  for (int i = 0; i < 5; i++) {
      file[i].stop();
      file[i].jump(i * file[i].duration() / 25); 
  }
  for (int i = 0; i < words.length; i++) {
    showStatic[i] = false;
    staticAlpha[i] = 255;
  }
  // Reset static "fire me" phrase
  staticFireMe = text.toShape("fire me");
  staticFireMePoints = new RPoint[splitGlyph];
  for (int j = 0; j < splitGlyph; j++) {
    staticFireMePoints[j] = staticFireMe.getPoint(j * frac);
  }

  // Reset positions for static phrases
  for (int i = 0; i < tokens[0].length; i++) {
    for (int j = 0; j < splitGlyph; j++) {
      posStaticX[0][i][j] = shiftPntX[i][0][j];
      posStaticY[0][i][j] = shiftPntY[i][0][j];
    }
  }
  // Reset clocks if needed (or reset specific variables in the Clock class)
  for (int i = 0; i < clocks.length; i++) {
    for (int j = 0; j < clocks[i].length; j++) {
      //clocks[i][j].setTime(0, 0); // Reset clock time
    }
  }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void mousePressed() {
  resetProgram();
  shiftPitch = !shiftPitch;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class Clock {
  float cx, cy;
  float radius;
  float clockDiameter;
  float hourRadius;
  float minuteRadius;
  float startHours;
  float startMinutes;
  float h, m;
  color col;
  long startMillis;
  float currentMinutes;

  Clock(float _cx, float _cy, float _startHours, float _startMinutes, color _col) {
    radius = random(10, 25);
    minuteRadius = radius * 0.3;
    hourRadius = radius * 0.15;
    clockDiameter = radius * 1;
    col = _col;
    cx = _cx;
    cy = _cy;
    startHours = _startHours;
    startMinutes = _startMinutes;
    startMillis = millis();
  }

  void setPosition(float _cx, float _cy) {
    cx = _cx;
    cy = _cy;
  }

  void setTime(float _startHours, float _startMinutes) {
    startHours = _startHours;
    startMinutes = _startMinutes;
  }

  void update() {
    long elapsedMillis = millis() - startMillis;
    float elapsedSeconds = (elapsedMillis / 1000.0) * timeFactor;
    float elapsedMinutes = elapsedSeconds / 60.0;
    float elapsedHours = elapsedSeconds / 3600.0;

    float currentHours = startHours + elapsedHours;
    float currentMinutes = startMinutes + elapsedMinutes;

    currentHours = currentHours % 24;
    currentMinutes = currentMinutes % 60;

    h = map(currentHours, 0, 12, 0, TWO_PI) - HALF_PI;
    m = map(currentMinutes, 0, 60, 0, TWO_PI) - HALF_PI;
  }

  void display() {
    col = color(250, 200, 0);
    noStroke();
    fill(col, map(m, 0, TWO_PI, 50, 255));

    arc(cx, cy, minuteRadius * 2, minuteRadius * 2, -HALF_PI, m);

    stroke(255, 150);
    strokeWeight(1.5);

    line(cx, cy, cx + cos(h) * hourRadius, cy + sin(h) * hourRadius);
    strokeWeight(1);
    line(cx, cy, cx + cos(m) * minuteRadius, cy + sin(m) * minuteRadius);
  }
}
1 Like