type1
type1
Log in to post a comment.
// Forked from "Packed in for a Good Wander" by ProjectGrantwood
// https://turtletoy.net/turtle/04187a129d
//
// Forked from "Beadpackpathwander" by ProjectGrantwood
// https://turtletoy.net/turtle/5f76422d98
Canvas.setpenopacity(1);
// ============================================================
// TEXT SETTINGS
// ============================================================
const TEXT = "SALAM";
// اندازه کلی نوشته
const TEXT_SCALE = 1.0;
// فاصله بین حروف
const LETTER_SPACING = 6;
// فاصله بین کلمات
const WORD_SPACING = 18;
// ضخامت / فاصله نقاط حروف
const LETTER_UNIT_SIZE = 4;
// ============================================================
// LETTER SIZE
// ============================================================
const LETTER_WIDTH = 5 * LETTER_UNIT_SIZE;
const LETTER_HEIGHT = 7 * LETTER_UNIT_SIZE;
// ============================================================
// TURTLE SETTINGS
// ============================================================
let shrinkageAcceleration = 50.0;
let shrinkageAcceleration_FineAdjust = 0.0;
let AngleIncrementA =-32; // min=-180, max=180, step=1
let AngleIncrementA_FineAdjust = 0.75; // min=0, max=1, step=0.01
let AngleIncrementB = 57; // min=-180, max=180, step=1
let AngleIncrementB_FineAdjust = 0.67; // min=0, max=1, step=0.01
let minimumRadius = 0.88; // min=0.05, max=4, step=0.01
let initialRadius = LETTER_UNIT_SIZE;
let maxFailures = 26; // min=0, max=180, step=1
shrinkageAcceleration =
(101 - (shrinkageAcceleration + shrinkageAcceleration_FineAdjust)) / 1000;
initialRadius =
minimumRadius > initialRadius
? minimumRadius
: initialRadius;
const angleIncrement1 =
(AngleIncrementA + AngleIncrementA_FineAdjust) * Math.PI / 180;
const angleIncrement2 =
(AngleIncrementB + AngleIncrementB_FineAdjust) * Math.PI / 180;
const t = new Turtle();
// ============================================================
// 5 x 7 FONT
// ============================================================
const FONT = {
A: [
"10001",
"10001",
"10001",
"11111",
"10001",
"10001",
"01110"
],
B: [
"11110",
"10001",
"10001",
"11110",
"10001",
"10001",
"11110"
],
C: [
"01111",
"10000",
"10000",
"10000",
"10000",
"10000",
"01111"
],
D: [
"11110",
"10001",
"10001",
"10001",
"10001",
"10001",
"11110"
],
E: [
"11111",
"10000",
"10000",
"11110",
"10000",
"10000",
"11111"
],
F: [
"10000",
"10000",
"10000",
"11110",
"10000",
"10000",
"11111"
],
G: [
"01111",
"10001",
"10001",
"10111",
"10000",
"10000",
"01111"
],
H: [
"10001",
"10001",
"10001",
"11111",
"10001",
"10001",
"10001"
],
I: [
"11111",
"00100",
"00100",
"00100",
"00100",
"00100",
"11111"
],
J: [
"01100",
"10010",
"00010",
"00010",
"00010",
"00010",
"00111"
],
K: [
"10001",
"10010",
"10100",
"11000",
"10100",
"10010",
"10001"
],
L: [
"11111",
"10000",
"10000",
"10000",
"10000",
"10000",
"10000"
],
M: [
"10001",
"10001",
"10001",
"10101",
"10101",
"11011",
"10001"
],
N: [
"10001",
"10011",
"10011",
"10101",
"11001",
"11001",
"10001"
],
O: [
"01110",
"10001",
"10001",
"10001",
"10001",
"10001",
"01110"
],
P: [
"10000",
"10000",
"10000",
"11110",
"10001",
"10001",
"11110"
],
Q: [
"01101",
"10010",
"10101",
"10001",
"10001",
"10001",
"01110"
],
R: [
"10001",
"10010",
"10100",
"11110",
"10001",
"10001",
"11110"
],
S: [
"11110",
"00001",
"00001",
"01110",
"10000",
"10000",
"01111"
],
T: [
"00100",
"00100",
"00100",
"00100",
"00100",
"00100",
"11111"
],
U: [
"01110",
"10001",
"10001",
"10001",
"10001",
"10001",
"10001"
],
V: [
"00100",
"01010",
"10001",
"10001",
"10001",
"10001",
"10001"
],
W: [
"10001",
"11011",
"10101",
"10101",
"10001",
"10001",
"10001"
],
X: [
"10001",
"10001",
"01010",
"00100",
"01010",
"10001",
"10001"
],
Y: [
"00100",
"00100",
"00100",
"00100",
"01010",
"10001",
"10001"
],
Z: [
"11111",
"10000",
"01000",
"00100",
"00010",
"00001",
"11111"
],
// ========================================================
// NUMBERS
// ========================================================
"0": [
"01110",
"10001",
"11001",
"10101",
"10011",
"10001",
"01110"
],
"1": [
"01110",
"00100",
"00100",
"00100",
"00100",
"01100",
"00100"
],
"2": [
"11111",
"01000",
"00100",
"00010",
"00001",
"10001",
"01110"
],
"3": [
"11110",
"00001",
"00001",
"01110",
"00001",
"00001",
"11110"
],
"4": [
"00010",
"00010",
"11111",
"10010",
"01010",
"00110",
"00010"
],
"5": [
"11110",
"00001",
"00001",
"11110",
"10000",
"10000",
"11111"
],
"6": [
"01110",
"10001",
"10001",
"11110",
"10000",
"10000",
"01110"
],
"7": [
"01000",
"01000",
"00100",
"00010",
"00001",
"00001",
"11111"
],
"8": [
"01110",
"10001",
"10001",
"01110",
"10001",
"10001",
"01110"
],
"9": [
"01110",
"00001",
"00001",
"01111",
"10001",
"10001",
"01110"
],
// ========================================================
// SPECIAL CHARACTERS
// ========================================================
"!": [
"00100",
"00000",
"00100",
"00100",
"00100",
"00100",
"00100"
],
"?": [
"00100",
"00000",
"00100",
"00010",
"00001",
"10001",
"01110"
],
"-": [
"00000",
"00000",
"00000",
"11111",
"00000",
"00000",
"00000"
],
"_": [
"11111",
"00000",
"00000",
"00000",
"00000",
"00000",
"00000"
],
".": [
"00110",
"00110",
"00000",
"00000",
"00000",
"00000",
"00000"
]
};
// ============================================================
// CREATE CIRCLE
// ============================================================
function createCircle(
x,
y,
rad,
heading,
connectRadii = "00"
) {
return [
x,
y,
rad,
heading,
connectRadii
];
}
// ============================================================
// TEXT → CIRCLES
// ============================================================
function textToCircles(text) {
let circles = [];
text = text.toUpperCase();
const charWidth =
LETTER_WIDTH + LETTER_SPACING;
const totalWidth =
getTextWidth(text);
let startX =
-totalWidth / 2;
for (let charIndex = 0; charIndex < text.length; charIndex++) {
const char = text[charIndex];
// فاصله
if (char === " ") {
startX += WORD_SPACING;
continue;
}
const pattern = FONT[char];
// اگر کاراکتر ناشناخته بود
if (!pattern) {
startX += charWidth;
continue;
}
for (let row = 0; row < pattern.length; row++) {
for (let col = 0; col < pattern[row].length; col++) {
if (pattern[row][col] !== "1") {
continue;
}
const x =
startX +
col * LETTER_UNIT_SIZE;
const y =
LETTER_HEIGHT / 2 -
row * LETTER_UNIT_SIZE;
circles.push(
createCircle(
x,
y,
LETTER_UNIT_SIZE / 2,
-Math.PI / 2,
"00"
)
);
}
}
startX += charWidth;
}
return circles;
}
// ============================================================
// TEXT WIDTH
// ============================================================
function getTextWidth(text) {
let width = 0;
for (let i = 0; i < text.length; i++) {
if (text[i] === " ") {
width += WORD_SPACING;
} else {
width += LETTER_WIDTH + LETTER_SPACING;
}
}
return width - LETTER_SPACING;
}
// ============================================================
// CREATE TEXT
// ============================================================
let circleArray = textToCircles(TEXT);
// ============================================================
// SCALE TEXT
// ============================================================
if (TEXT_SCALE !== 1) {
for (let c of circleArray) {
c[0] *= TEXT_SCALE;
c[1] *= TEXT_SCALE;
c[2] *= TEXT_SCALE;
}
}
// ============================================================
// FIND TEXT BOUNDS
// ============================================================
function centerCircles(circles) {
if (circles.length === 0) {
return;
}
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
for (let c of circles) {
minX = Math.min(minX, c[0]);
maxX = Math.max(maxX, c[0]);
minY = Math.min(minY, c[1]);
maxY = Math.max(maxY, c[1]);
}
const centerX =
(minX + maxX) / 2;
const centerY =
(minY + maxY) / 2;
for (let c of circles) {
c[0] -= centerX;
c[1] -= centerY;
}
}
centerCircles(circleArray);
// ============================================================
// PACKING SETTINGS
// ============================================================
let ratio = 0.8;
let ratio2 = 0.999;
const initialRatio = ratio;
let currentCircle =
circleArray[0];
let failures = 0;
// ============================================================
// TURTLE
// ============================================================
t.radians();
t.pd();
let time = 0;
// ============================================================
// WALK
// ============================================================
function walk() {
if (time === 0) {
for (let z = 0; z < circleArray.length; z++) {
draw(
circleArray[z],
t,
"10"
);
}
time += 1;
} else {
for (let j = 0; j < 200; j++) {
currentCircle =
getAndCheck(
currentCircle,
circleArray,
time
);
}
time += 1;
}
return time < 20000;
}
// ============================================================
// DRAW
// ============================================================
function draw(
c,
turtle,
i,
flags = c[4]
) {
turtle.jmp(
c[0],
c[1] - c[2]
);
// Circle
if (flags[0] === "1") {
turtle.jmp(
c[0],
c[1]
);
turtle.circle(
c[2]
);
}
// Connecting line
if (flags[1] === "1") {
turtle.jmp(
c[0],
c[1]
);
turtle.seth(
c[3] - Math.PI
);
let travelDistance =
c[2];
travelDistance =
c[2] === minimumRadius
? c[2] + c[2] + minimumRadius
: c[2] + c[2] / ratio + minimumRadius;
turtle.forward(
travelDistance
);
}
}
// ============================================================
// GET NEXT CIRCLE
// ============================================================
function getNextCircle(c) {
let radiusOld =
c[2];
let radiusNew =
radiusOld * ratio;
radiusNew =
radiusNew < minimumRadius
? minimumRadius
: radiusNew;
let headingNew =
c[3] + angleIncrement1;
c[3] =
headingNew;
let d =
(radiusOld + radiusNew) +
minimumRadius;
let xOld =
c[0];
let yOld =
c[1];
let xNew =
xOld +
d * Math.cos(headingNew);
let yNew =
yOld +
d * Math.sin(headingNew);
return createCircle(
xNew,
yNew,
radiusNew,
headingNew,
"01"
);
}
// ============================================================
// BOUNDS
// ============================================================
function checkBounds(c) {
let add = true;
add &=
c[0] <
100 - MARGIN - c[2];
add &=
c[0] >
-100 + MARGIN + c[2];
add &=
c[1] <
100 - MARGIN - c[2];
add &=
c[1] >
-100 + MARGIN + c[2];
return add;
}
// ============================================================
// COLLISION + WALK
// ============================================================
function getAndCheck(
c1,
circleArray,
i
) {
let c2 =
getNextCircle(
currentCircle
);
let add = 1;
add *=
checkBounds(c2);
let newC;
if (add) {
for (let c of circleArray) {
add *=
checkCircleOverlap(
c,
c2
);
if (!add) {
newC = c;
break;
}
}
}
if (!add) {
c1[3] +=
angleIncrement2;
failures++;
if (failures >= maxFailures) {
failures = 0;
c1[3] +=
angleIncrement2;
ratio *=
initialRatio +
shrinkageAcceleration;
let radius =
initialRadius;
let index =
circleArray.indexOf(c1) - 1;
index =
index < 0 ||
Math.random() < 0.04
? Math.floor(
Math.random() *
circleArray.length
)
: index;
return circleArray[index];
}
return currentCircle;
} else {
draw(
c2,
t,
i
);
circleArray.push(
c2
);
return c2;
}
}
// ============================================================
// OVERLAP CHECK
// ============================================================
function checkCircleOverlap(
c1,
c2
) {
const xd =
(c2[0] - c1[0]) ** 2;
const yd =
(c2[1] - c1[1]) ** 2;
const dsq =
xd + yd;
return (
dsq >=
(c1[2] + c2[2]) ** 2
);
}
// ============================================================
// MAP
// ============================================================
function map(
val,
hi,
lo,
newhi,
newlo
) {
return (
(val - lo) *
(newhi - newlo)
) / (hi - lo) + newlo;
}
// ============================================================
// MARGIN
// ============================================================
const MARGIN =
LETTER_UNIT_SIZE * 3.5;