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);
}