Tangram Inspired Triangle Drawer

Tangram Triangle Drawer

The Tangram Triangle Drawer is an interactive visual application designed to create a dynamic representation of triangles perfectly fitting inside a square, resembling a tangram pattern.

This is part of a larger and wider software that I am developing that allows generating fractals and interesting geometric shapes and I decided to share this part I guess there are already similar works but I decided to share the work anyway in case someone wants to play with it

final int SQUARE_SIZE = 400;
final int SLIDER_MIN = 2;
final int SLIDER_MAX = 10000;
final int UI_WIDTH = 300;
final int MAX_ZOOM = 10;
final int MIN_ZOOM = 1;

int triangleCount = 2;
int activeSlider = -1;
boolean useHSL = false;

float scale = 1;
float targetScale = 1;
float translateX = 0;
float translateY = 0;
float targetTranslateX = 0;
float targetTranslateY = 0;

float lastMouseX, lastMouseY;

void setup() {
  size(800, 600);
  textFont(createFont("Arial", 14));
  colorMode(HSB, 360, 100, 100);
}

void draw() {
  background(240);
  scale = lerp(scale, targetScale, 0.1);
  translateX = lerp(translateX, targetTranslateX, 0.1);
  translateY = lerp(translateY, targetTranslateY, 0.1);
  applyTransformations();
  drawGrid();
  drawSquareWithTriangles(triangleCount);
  resetMatrix();
  drawUI();
}

void applyTransformations() {
  translate(width / 2 + translateX, height / 2 + translateY);
  scale(scale);
  translate(-width / 2, -height / 2);
}

void drawUI() {
  fill(40, 114);
  noStroke();
  rect(0, 0, UI_WIDTH, height);
  fill(255);
  textAlign(LEFT, CENTER);
  textSize(14);
  drawSlider("Triangles: ", triangleCount, 20, 30, SLIDER_MIN, SLIDER_MAX);
  drawToggleButton("Use HSL", 20, 80, useHSL);
}

void drawSlider(String label, int value, float x, float y, int minValue, int maxValue) {
  fill(255);
  text(label + value, x, y);
  fill(60);
  rect(x, y + 10, 260, 10, 5);
  float sliderPosition = map(log(value), log(minValue), log(maxValue), x, x + 260);
  fill(200);
  rect(sliderPosition - 5, y + 5, 10, 20, 5);
  if (mouseX > sliderPosition - 5 && mouseX < sliderPosition + 5 && mouseY > y + 5 && mouseY < y + 25) {
    cursor(HAND);
  } else {
    cursor(ARROW);
  }
}

void drawSquareWithTriangles(int count) {
  float x = width / 2 - SQUARE_SIZE / 2 + 150;
  float y = height / 2 - SQUARE_SIZE / 2;
  fill(255);
  rect(x, y, SQUARE_SIZE, SQUARE_SIZE);
  drawTangramTriangles(count, x, y, SQUARE_SIZE);
}

void drawTangramTriangles(int count, float x, float y, float size) {
  int rows = (int) sqrt(count);
  float triangleSize = size / rows;
  for (int i = 0; i < rows; i++) {
    for (int j = 0; j < rows; j++) {
      color c = getColor(i, j, rows);
      drawTrianglePattern(x + j * triangleSize, y + i * triangleSize, triangleSize, c);
    }
  }
}

color getColor(int i, int j, int rows) {
  if (useHSL) {
    return colorHSL((float)(i + j) / (rows * 2));
  } else {
    float red = map(i, 0, rows - 1, 255, 200);
    float green = map(j, 0, rows - 1, 100, 200);
    float blue = map(i + j, 0, rows * 2 - 2, 100, 255);
    return color(red, green, blue);
  }
}

void drawTrianglePattern(float x, float y, float size, color c) {
  fill(c);
  triangle(x, y, x + size, y, x, y + size);
  fill(lerpColor(c, color(255), 0.3));
  triangle(x + size, y, x + size, y + size, x, y + size);
}

void drawGrid() {
  stroke(200);
  for (int i = -width * 10; i < width * 10; i += 50) {
    line(i, -height * 10, i, height * 10);
  }
  for (int j = -height * 10; j < height * 10; j += 50) {
    line(-width * 10, j, width * 10, j);
  }
}

void mousePressed() {
  if (mouseX > 20 && mouseX < 280 && mouseY > 35 && mouseY < 55) {
    activeSlider = 0;
  } else if (mouseX > 170 && mouseX < 210 && mouseY > 70 && mouseY < 90) {
    useHSL = !useHSL;
  }
  lastMouseX = mouseX;
  lastMouseY = mouseY;
}

void mouseDragged() {
  if (activeSlider == 0) {
    float sliderValue = map(constrain(mouseX, 20, 280), 20, 280, log(SLIDER_MIN), log(SLIDER_MAX));
    triangleCount = int(exp(sliderValue));
  } else {
    float dx = mouseX - lastMouseX;
    float dy = mouseY - lastMouseY;
    targetTranslateX += dx;
    targetTranslateY += dy;
    lastMouseX = mouseX;
    lastMouseY = mouseY;
  }
}

void mouseWheel(MouseEvent event) {
  float e = event.getCount();
  float zoomFactor = 0.05;
  float zoomAmount = 1 + (e > 0 ? -zoomFactor : zoomFactor);
  targetScale *= zoomAmount;
  targetScale = constrain(targetScale, MIN_ZOOM, MAX_ZOOM);
  float mouseXWorld = (mouseX - (width / 2 + translateX)) / scale;
  float mouseYWorld = (mouseY - (height / 2 + translateY)) / scale;
  targetTranslateX -= mouseXWorld * (targetScale - scale);
  targetTranslateY -= mouseYWorld * (targetScale - scale);
}

void mouseReleased() {
  activeSlider = -1;
}

void drawToggleButton(String label, float x, float y, boolean state) {
  fill(255);
  text(label, x, y);
  color onColor = #00FF7F;
  color offColor = #000000;
  fill(state ? onColor : offColor);
  rect(x + 150, y - 10, 40, 20, 10);
  fill(255);
  if (state) {
    ellipse(x + 180, y, 15, 15);
  } else {
    ellipse(x + 160, y, 15, 15);
  }
}

color colorHSL(float position) {
  float hue = map(position, 0, 1, 0, 360);
  return color(hue, 80, 90);
}
4 Likes

Very pretty :smile:

I had not heard of Tangram triangles so I googled it. All the hits I got suggested it differs from what your creation. :thinking:

3 Likes

Neat. Isn’t this more of a “tiling” generator? Or “tessellation”? From here it should be easy enough to expand it with other tile-able polygons. And then after that, Penrose tiling. :wink:

Though I also fail to see any connection to Tangram.

1 Like

True, both you and @eightohnine are correct.
when i was young i played tangram, my idea was to create a tangram with triangles only that can fit inside it, after re-checking the meaning of the word i realized that i can’t find a tangram with triangles only, well… ill probably make a tangram generator as well that can fit in any number of shapes into another shape.

4 Likes