xxxxxxxxxx
656
// shoutout mohamad aljanabi https://youtu.be/Cv7Sbuuo2X8
// ok so the base version works but if i want this to be viable i need:
// > rasterization improvements. theres weird mistakes sometimes & generally i feel like it could be improved if i try implementing some of the methods i wrote down
// > checks on how many colors in total/simultaneously per row
// > float length checks.
// variables:
// basically my ratios. maybe i can tweak em
const gp1 = 25;
const gp2 = 50;
let gp3 = 10; // 100/gp3 = the divisions of the square
let startingPoint = [0, 100];
let conPts = [];
let lPts = [], lPts2 = [];
let segs = [], segs2 = [];
let intersections = [];
let polygons = [];
let prevDir;
let errorBreaker = 0;
let slider;
//for knitting
const HOR_ST = 64;
const VER_ST = 64;
// const RES = 20;
const TEXSIZE = 32;
let drawSize;
let pal = ["#152A84", "#776EAA", "#201315", "#DB1D6B", "#CDE4CF", "#008460"];
let bmp = [];
function setup() {
// pixelDensity(1);
for(let y=0;y<TEXSIZE;y++){
bmp.push([]);
for(let x=0;x<TEXSIZE;x++){
bmp[y].push(0);
}
}
createCanvas(560, 560);
strokeWeight(0.5);
// stroke(255);
noStroke();
strokeCap(SQUARE);
angleMode(DEGREES);
slider = createSlider(1, 8, 2);
slider.style("width", width + "px");
createButton("regenerate")
.mousePressed(regen)
.position(10, height + 30);
regen();
}
function draw() {
background(255);
drawSize = width / (TEXSIZE * (slider.value()));
for(let x=0;x<TEXSIZE;x++){
for(let y=0;y<TEXSIZE;y++){
fill(pal[bmp[y][x]]);
// stroke(pal[bmp[y][x]])
for(let i=0;i<slider.value();i++){
for(let j=0;j<slider.value();j++){
drawStitch(i*(TEXSIZE*2)+ x, j*(TEXSIZE*2)+ y);
drawStitch(i*(TEXSIZE*2)+ TEXSIZE*2-1 - x, j*(TEXSIZE*2)+ y);
drawStitch(i*(TEXSIZE*2)+ x, j*(TEXSIZE*2)+ TEXSIZE*2 - 1 - y);
drawStitch(i*(TEXSIZE*2)+ TEXSIZE*2-1 - x, j*(TEXSIZE*2)+ TEXSIZE*2 - 1 - y);
}
}
}
}
print('stitches per row: ' + slider.value()*TEXSIZE);
noLoop();
}
function regen() {
constructionPoints();
// ok now it has to actually make the lines between them lmao
// traditional tiling: mirror hor and vert to make a tile,
// rules:
// 1. diagonal symmetry: R should match B and T should match L.
// 2. prevent infinite tiles: this happens when there is an uninterrupted connection between T&B or L&R.
// 3. no 180 degree turns!
// 4. (optional) no uninterrupted horizontal or vertical lines across the full length
generatePath();
// print('points on line: ' + lPts.length);
findIntersections();
// print('segments: ' + segs.length);
calculatePolygons();
writeBitmap();
shuffle(pal, true);
draw();
}
function writeBitmap(){
for(let n=0;n<polygons.length;n++){
for(let y=0;y<TEXSIZE;y++){
let drawing = false;
// do a scanline
let scy=y+0.5;
let scx=[];
for(let i=0;i<polygons[n].length;i++){
let p1= {x:polygons[n][i][0] / 6.25,
y:polygons[n][i][1] / 6.25};
let p2={x:polygons[n][(i+1)%polygons[n].length][0] / 6.25,
y:polygons[n][(i+1)%polygons[n].length][1] / 6.25};
if( (scy-p1.y) * (scy-p2.y) > 0 ){ continue; } //check if they intersect at all
scx.push( p1.x + (p2.x-p1.x) * ((scy-p1.y)/(p2.y-p1.y)) );
}
scx.sort(function (a, b) { return a-b; }); //this function return shit stops it from sorting alphabetically but idk why
for(let x=0;x<TEXSIZE;x++){
//check for intersections: this is where rasterization changes go
let pass = x+0.5 > scx[0]
if(pass){
drawing = 1 - drawing;
scx.shift();
}
if(drawing==1){
bmp[y][x] = (polygons[n].length-3) % pal.length;
}
}
}
}
}
function constructionPoints() {
// generate construction points
conPts = [];
for (let i = gp1; i < gp1 * 16; i += 2 * gp1) {
let startx = floor(i/100) * gp1;
let starty = (i % (gp1 * 8)) % (gp1 * 5);
conPts.push(
[startx + gp3, starty],
[startx - gp3, starty],
[startx, starty + gp3],
[startx, starty - gp3]
);
}
// move points around a bit
for (let i = 0; i < conPts.length * 2; i++) {
if (conPts[floor(i / 2)][i % 2] < 0) {
conPts[floor(i / 2)][i % 2] = conPts[floor(i / 2)][i % 2] + 100;
} else if (conPts[floor(i / 2)][i % 2] == 0) {
conPts.push([0.01, 0.01]);
conPts[conPts.length - 1][i % 2] = conPts[floor(i / 2)][i % 2] + 100;
conPts[conPts.length - 1][1 - (i % 2)] =
conPts[floor(i / 2)][1 - (i % 2)];
}
}
conPts.push([0, 0], [0, 100], [100, 0], [100, 100]); //add the corners
}
function generatePath() {
let hitDia = false;
lPts = [startingPoint];
for (let i = 0; i < 100; i++) {
// down, right, up, left, sw, nw, ne, se
let dirs = [[], [], [], [], [], [], [], []];
// check availabile jumps
for (let j = 0; j < conPts.length; j++) {
// check orth directions
for (let k = 0; k < 2; k++) {
if (conPts[j][k] == lPts[i][k] && conPts[j][1 - k] != lPts[i][1 - k]) {
//x, y
dirs[k + 1 + Math.sign(lPts[i][1 - k] - conPts[j][1 - k])].push(j);
}
}
// check diagonal directions
for (let k = -1; k < 2; k += 2) {
if (lPts[i][0] - conPts[j][0] == k * (lPts[i][1] - conPts[j][1]) &&
conPts[j][0] != lPts[i][0] ) {
dirs[5 + (1 + k) / 2 + Math.sign(conPts[j][0] - lPts[i][0])].push(j);
}
}
}
// choose direction
let dir = findDir(dirs, hitDia);
let newpoint = conPts[dirs[dir][floor(random(0, dirs[dir].length))]];
errorBreaker = 0;
while(abs(lPts[i][0] - newpoint[0]) == 100 ||
abs(lPts[i][1] - newpoint[1]) == 100){
if(dirs[dir].length == 1){
dir = findDir(dirs, hitDia);
}
newpoint = conPts[dirs[dir][floor(random(0, dirs[dir].length))]];
errorBreaker++;
if (errorBreaker > 50) {
print("error 2!");
print(lPts[i], dirs);
break;
}
}
prevDir = dir;
lPts.push([newpoint[0], newpoint[1]]);
if (lPts[i + 1][0] > lPts[i + 1][1]) {
hitDia = true;
}
if (hitDia && lPts[i + 1][0] == 0) {
break;
}
}
lPts2 = [];
for (let i = 0; i < lPts.length; i++) {
lPts2.push([lPts[i][1], lPts[i][0]]);
}
}
function findDir(dirs, hitDia){
let dir = floor(random(0, 8));
errorBreaker = 0;
while (
(dir >= 3 && dir <= 5 && hitDia == false) ||
dirs[dir].length == 0 ||
dir % 4 == (prevDir + 2) % 4 ||
dir == prevDir
) {
dir = floor(random(0, 8));
errorBreaker++;
if (errorBreaker > 50) {
print("error 1!");
break;
}
}
return dir;
}
function findIntersections(){
// find intersections between lines and append
// cut em up into shortest segments
segs = [];
//turn them all into segments
for(let i=0; i<lPts.length-1; i++){
segs.push([lPts[i].slice(),lPts[i+1].slice()])
segs.push([lPts2[i].slice(),lPts2[i+1].slice()])
}
//find all the intersections
let currentLength = segs.length;
for(let i=0; i<currentLength; i++){
//check intersections
for(let j=0;j<currentLength;j++){
// check self intersections
let intersection = intersect (segs[i], segs[j])
if(intersection &&
!(segs[i][0][0] == intersection[0] && segs[i][0][1] == intersection[1]) &&
!(segs[i][1][0] == intersection[0] && segs[i][1][1] == intersection[1])){
let coords = [segs[i][0],intersection]
segs.push(coords);
}
}
}
//cut up overlapping ones, this function is so fucking bad holy shit also it creates wayyy too many
currentLength = segs.length;
for(let i=0;i<currentLength;i++){
for(let j=i+1;j<currentLength;j++){
if(overlap(segs[i],segs[j]) &&
!(segs[i][0][0] == segs[j][0][0] && segs[i][0][1] == segs[j][0][1] &&
segs[i][1][0] == segs[j][1][0] && segs[i][1][1] == segs[j][1][1]) &&
!(segs[i][0][0] == segs[j][1][0] && segs[i][0][1] == segs[j][1][1] &&
segs[i][1][0] == segs[j][0][0] && segs[i][1][1] == segs[j][0][1]) ){
//check if vertical
let dir = 0;
if( segs[i][0][0] == segs[i][1][0]){
dir = 1;
}
let orderedSegmentPoints = [];
let orderingArray = [ [segs[i][0][0],segs[i][0][1]],
[segs[i][1][0],segs[i][1][1]],
[segs[j][0][0],segs[j][0][1]],
[segs[j][1][0],segs[j][1][1]]];
let orderingArray2 = [segs[i][0][dir],
segs[i][1][dir],
segs[j][0][dir],
segs[j][1][dir]];
for(let k=0;k<4;k++){
for(let l=0;l<orderingArray.length;l++){
if(orderingArray2[l] == min(orderingArray2)){
orderedSegmentPoints.push(orderingArray[l].slice())
orderingArray.splice(l,1);
orderingArray2.splice(l,1);
break;
}
}
}
for(let k=0;k<3;k++){
if(orderedSegmentPoints[k][dir] != orderedSegmentPoints[k+1][dir]){
segs.push([orderedSegmentPoints[k].slice(), orderedSegmentPoints[k+1].slice()])
}
}
}
}
}
//filter duplicates/overlap
for(let i=0;i<segs.length;i++){
for(let j=0;j<segs.length;j++){
if(i!=j && overlap(segs[i],segs[j])){
// delete the longest one!!!!
let segLi = (segs[i][0][0]-segs[i][1][0])**2 + (segs[i][0][1]-segs[i][1][1])**2 ;
let segLj = (segs[j][0][0]-segs[j][1][0])**2 + (segs[j][0][1]-segs[j][1][1])**2 ;
if(segLi > segLj){
segs.splice(i,1)
i--;
break;
}
if(segLi <= segLj){
segs.splice(j,1)
if(j<i){
i--;
}
j--;
}
}
}
}
}
function intersect(c1,c2){
// how to find a single intersection?
// -> between two lines? between a line and every other line? between my dick and your ass, cockboy?
// bunch of stolen code
let cmP = [c2[0][0] - c1[0][0], c2[0][1] - c1[0][1]];
let r = [c1[1][0] - c1[0][0], c1[1][1] - c1[0][1]];
let s = [c2[1][0] - c2[0][0], c2[1][1] - c2[0][1]];
let cmPxr = cmP[0] * r[1] - cmP[1] * r[0];
let cmPxs = cmP[0] * s[1] - cmP[1] * s[0];
let rxs = r[0] * s[1] - r[1] * s[0];
// lines are collinear or parallel
if (cmPxr == 0 || rxs == 0) {
return false;
}
let rxsr = 1 / rxs;
let t = cmPxs * rxsr;
let u = cmPxr * rxsr;
if((t < 0) || (t > 1) || (u < 0) || (u > 1)){
return false
}
let inxs = [ round(c1[0][0] + t * r[0]) , round(c1[0][1] + t * r[1]) ]
return inxs
}
function overlap(c1, c2){
// bunch of stolen code, same as intersect
let cmP = [c2[0][0] - c1[0][0], c2[0][1] - c1[0][1]];
let r = [c1[1][0] - c1[0][0], c1[1][1] - c1[0][1]];
let s = [c2[1][0] - c2[0][0], c2[1][1] - c2[0][1]];
let cmPxr = cmP[0] * r[1] - cmP[1] * r[0];
let rxs = r[0] * s[1] - r[1] * s[0];
// check parallel & aligned
if (cmPxr != 0 || rxs != 0) {
return false;
}
//check if vertical
let dir = 0;
if(c1[0][0] == c1[1][0]){
dir = 1;
}
// find overlap
if(max(c1[0][dir],c1[1][dir]) > min(c2[0][dir],c2[1][dir]) &&
max(c2[0][dir],c2[1][dir]) > min(c1[0][dir],c1[1][dir]) ){
return true;
}
return false;
}
function calculatePolygons(){
// i think this is the algorithm for finding a polygon:
// 1. start at a segment, write down its first coord and go in the direction of its second coord.
// 2. for the second coord, find all the other segments that carry it and find the one that is most clockwise, then go to that ones other coord
// 3. repeat until we're back at the starting coord
// if starting at a coord we already did, make sure to go in the other direction!
// also if the point is on the border dont forget to check mirrored points outside the border as well or we'll just loop around the edge lamo
// we know we're done when each line segment is walked TWICE and only ONCE PER POLYGON. with the exception of segments on or outside the border which are walked ONCE IN TOTAL.
// to check if each segment has all its polygons we make a list of conditions for how often it's been travelled [0] for its normal direction [0]->[1] and [1] for its reverse direction [1]->[0]. with true meaning it has been traversed or in the case of the border segments, that it does not need to be traversed.
// segments on the top and left b
let segmentStatus = [];
for(let i=0; i<segs.length;i++){
//some tricky evaluations to see in which direction the border ones need to go to be clockwise
if( ((segs[i][0][0] == 0 && segs[i][1][0] == 0) &&
segs[i][0][0] + segs[i][0][1] < segs[i][1][0] + segs[i][1][1]) ||
((segs[i][0][0] == 100 && segs[i][1][0] == 100) &&
segs[i][0][0] + segs[i][0][1] > segs[i][1][0] + segs[i][1][1]) ||
((segs[i][0][1] == 0 && segs[i][1][1] == 0) &&
segs[i][0][0] + segs[i][0][1] > segs[i][1][0] + segs[i][1][1]) ||
((segs[i][0][1] == 100 && segs[i][1][1] == 100) &&
segs[i][0][0] + segs[i][0][1] < segs[i][1][0] + segs[i][1][1])){
segmentStatus.push([false, true]);
}
else if((segs[i][0][0] == 0 && segs[i][1][0] == 0) ||
(segs[i][0][0] == 100 && segs[i][1][0] == 100) ||
(segs[i][0][1] == 0 && segs[i][1][1] == 0) ||
(segs[i][0][1] == 100 && segs[i][1][1] == 100) ){
segmentStatus.push([true, false]);
}
//all others need both directions
else{
segmentStatus.push([false,false]);
}
}
let altSegs = [[[],[],[]],[[],[],[]],[[],[],[]]];
let m = [-1,1,-1], p = [0,0,200]
for(let i=0;i<segs.length;i++){
for(let xi=0;xi<3;xi++){
for(let yi=0;yi<3;yi++){
altSegs[xi][yi].push([ [segs[i][0][0] * m[xi] + p[xi], segs[i][0][1] * m[yi] + p[yi]],
[segs[i][1][0] * m[xi] + p[xi], segs[i][1][1] * m[yi] + p[yi]] ])
}
}
}
//ALL RIGHT BABY NOW WE CAN FINALLY MAKE SOME FUCKING POLYGONS WOOO
polygons = [];
while(true){
//find an unsatisfied segment, if there are none then break
let cSeg, direction;
for(let i=0;i<segmentStatus.length;i++){
if(!segmentStatus[i][0]){
cSeg = i;
direction = 0;
break;
}
if(!segmentStatus[i][1]){
cSeg = i;
direction = 1;
break;
}
}
if(cSeg == undefined){
break;
}
// HERE GOES POLYGON WALKER CODE
// exciting stuff
let newPolygon = [];
//add the points on the first segment
newPolygon.push(segs[cSeg][0+direction].slice(), segs[cSeg][1-direction].slice());
segmentStatus[cSeg][direction] = true;
while( !arrayEquals(newPolygon[0], newPolygon[newPolygon.length-1]) ){
let cPoint = newPolygon[newPolygon.length-1].slice();
let thisVect = [newPolygon[newPolygon.length - 2][0] - cPoint[0],
newPolygon[newPolygon.length - 2][1] - cPoint[1]];
let thisAngle = findAngle(thisVect);
let pickedIndex;
let nextAngle = 360;
let searchSegs = [];
for(let xi=0;xi<3;xi++){
for(let yi=0;yi<3;yi++){
if(cPoint[0] >= -100 + 100*xi && cPoint[0] <= -100 + 100*(xi+1) &&
cPoint[1] >= -100 + 100*yi && cPoint[1] <= -100 + 100*(yi+1)){
searchSegs = concat(searchSegs, altSegs[xi][yi])
}
}
}
//first find all segments starting at the latest point
for(let i=0;i<searchSegs.length*2;i++){
if( arrayEquals(searchSegs[floor(i/2)][i%2], newPolygon[newPolygon.length-1]) &&
!arrayEquals(searchSegs[floor(i/2)][1 - i%2], newPolygon[newPolygon.length-2])){
let nextPoint = searchSegs[floor(i/2)][1 - i%2];
let nextVect = [nextPoint[0] - cPoint[0], nextPoint[1] - cPoint[1]]
let angleD = ( thisAngle + 360 - findAngle(nextVect) ) %360;
if(angleD < nextAngle){
nextAngle = angleD;
pickedIndex = i;
}
}
}
//ERRORS OCCUR HERE -> pickedIndex=undefined, meaning it did not find ANY angle it could take -> possibly (probably) bc of randomly ending construction line
let pickedPoint = searchSegs[floor(pickedIndex/2)][1 - pickedIndex%2].slice()
newPolygon.push(pickedPoint.slice());
//mark this direction as done
if(cPoint[0] >= 0 && cPoint[1] >= 0 && pickedPoint[0] >= 0 && pickedPoint[1] >= 0 &&
cPoint[0] <= 100 && cPoint[1] <= 100 && pickedPoint[0] <= 100 && pickedPoint[1] <= 100){
segmentStatus[floor((pickedIndex%(segs.length*2))/2)][(pickedIndex%(segs.length*2))%2] = true;
}
if(arrayEquals(newPolygon[0], newPolygon[newPolygon.length-1])){
polygons.push(newPolygon);
}
}
newPolygon.pop();
}
}
function arrayEquals(ar1,ar2){
if(ar1 == undefined || ar2 == undefined){
return true
}
for(let i=0;i<ar1.length;i++){
if(ar1[i] != ar2[i]){
return false
}
}
return true
}
function findAngle(vect){
return -1 * acos( vect[0] / (sqrt(vect[0]*vect[0] + vect[1]*vect[1])) ) * Math.sign(vect[1]+0.01);
}
function drawStitch(x,y){
beginShape();
vertex((x+0.0)*drawSize,(y+0.0)*drawSize);
vertex((x+0.0)*drawSize,(y+1.0)*drawSize);
vertex((x+0.5)*drawSize,(y+1.3)*drawSize);
vertex((x+0.5)*drawSize,(y+0.3)*drawSize);
endShape(CLOSE);
beginShape();
vertex((x+1.0)*drawSize,(y+0.0)*drawSize);
vertex((x+0.5)*drawSize,(y+0.3)*drawSize);
vertex((x+0.5)*drawSize,(y+1.3)*drawSize);
vertex((x+1.0)*drawSize,(y+1.0)*drawSize);
endShape(CLOSE);
}
function mouseReleased(){
draw();
}