xxxxxxxxxx
677
// shoutout mohamad aljanabi https://youtu.be/Cv7Sbuuo2X8
// ok so now the hard part: turning the lines into uninterrupted polygons
// and then from that it makes polygon arrays for the actual tiles, could be similar to the voronoi construction method, along with an array of coordinates/rotation
// so now i have all segments, and i know each segment should border a polygon on two side, with the exception of segments on or outside the frame border, so i can loop over my segments until this condition is satisfied for each.
// then the function that draws all of them
// variables:
// basically my ratios. maybe i can tweak em
const gp1 = 25;
const gp2 = 50;
let gp3 = 10; //i think maybe actually this is the only one i can tweak without breaking 45 degree relations (and like; everything)
let startingPoint = [0, 100];
let conPts = [];
let lPts = [], lPts2 = [];
let segs = [], segs2 = [];
let intersections = [];
let polygons = [];
let polygonRepetitions = [];
let tileSize = 100;
let prevDir;
let errorBreaker = 0;
let slider;
function setup() {
createCanvas(800, 400);
strokeWeight(3);
noFill();
strokeCap(SQUARE);
angleMode(DEGREES);
slider = createSlider(10, width, min(height,width)/4, 0);
slider.style("width", width + "px");
createButton("regenerate")
.mousePressed(regen)
.position(10, height + 30);
createButton("save png")
.mousePressed(savePNG)
.position(90, height + 30);
createButton("save pattern data")
.mousePressed(saveCSV)
.position(160, height + 30);
regen();
}
function regen() {
noiseSeed(random(0,100));
constructionPoints();
generatePath();
// print('points on line: ' + lPts.length);
findIntersections();
// print('segments: ' + segs.length);
calculatePolygons();
// print('polygons: ' +polygons.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 = [];
polygonRepetitions = [];
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 = [];
//should it repeat? ->> [left, right, top, bottom] (if left&to)
polygonRepetitions.push([true,true,true,true]);
//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;
}
}
}
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;
}
// repeat killa? [tl,tr,bl,br]
if( pickedPoint[0] < 0 || pickedPoint[0] > 100 ){
polygonRepetitions[polygonRepetitions.length-1][1] = false;
polygonRepetitions[polygonRepetitions.length-1][3] = false;
}
if( pickedPoint[1] < 0 || pickedPoint[1] > 100 ){
polygonRepetitions[polygonRepetitions.length-1][2] = false;
polygonRepetitions[polygonRepetitions.length-1][3] = false;
}
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);
}
// DRAW FUNCTIONS
function draw() {
background(0);
// drawConstructionPoints();
// drawSegments(segs);
//ok now we wanna actually draw them all tiled.
//while i can reflect and tile all of them, this would result in polygons outside the border overlapping with themselves one or three times which just wouldnt sit right with me so check polygonRepetitions[i] first to see where it should go
tileSize = slider.value();
stroke(255);
noStroke();
for(let x= -1* ceil(width/(tileSize)); x<ceil(width/(tileSize));x++){
for(let y= -1* ceil(height/(tileSize)); y<ceil(height/(tileSize));y++ )
fourPatterns(width/2 + x*tileSize, height/2 + y*tileSize);
}
// regen(); //epillepsy mode
}
function fourPatterns(x,y){
//mirror the original twice
for(let i=0;i<4;i++){
let xori = i%2;
let yori = floor(i/2);
for(let j=0;j<polygons.length;j++){
if(polygonRepetitions[j][i] == false){
continue;
}
fill(noise(polygons[j].length)*255,
noise(polygons[j].length+20)*255,
noise(polygons[j].length+40)*255,
135+ noise(polygons[j].length)*40 +
noise(polygons[j].length+20)*40 +
noise(polygons[j].length+40)*40);
beginShape()
for(let k=0;k<polygons[j].length;k++){
vertex((1 - 2*xori) * polygons[j][k][0] * (tileSize/200) + xori * tileSize + x,
(1 - 2*yori) * polygons[j][k][1] * (tileSize/200) + yori * tileSize + y)
}
endShape(CLOSE)
}
}
}
function drawConstructionPoints(){
for(let i=0;i<conPts.length;i++){
stroke(255)
strokeWeight(3)
point(conPts[i][0] * width / 200 + 100, conPts[i][1] * height / 200 + 100);
noStroke()
fill(255)
text(i, conPts[i][0] * width / 200 + 100, conPts[i][1] * height / 200 + 100)
noFill()
stroke(255)
strokeWeight(3)
}
}
function drawSegments(segs, col=false){
for(let i=0;i<segs.length;i++){
if(col){stroke(col)}
else{stroke(55+(i*92)%200,55+(i*42)%200,55+(i*64)%200,200);}
let drawseg = [ [segs[i][0][0] * width / 100, segs[i][0][1] * height / 100] , [segs[i][1][0] * width / 100, segs[i][1][1] * height / 100] ];
strokeWeight(3)
line(drawseg[0][0], drawseg[0][1], drawseg[1][0], drawseg[1][1] )
}
}
// BONUS FEATURES I DONT CARE
function savePNG(){
saveCanvas()
}
function saveCSV(){
let data = new p5.Table();
data.addColumn('x');
data.addColumn('y');
for(let i=0;i<lPts.length;i++){
let newRow = data.addRow();
newRow.setNum('x', lPts[i][0]);
newRow.setNum('y', lPts[i][1]);
}
for(let i=0;i<lPts2.length;i++){
let newRow = data.addRow();
newRow.setNum('x', lPts2[i][0]);
newRow.setNum('y', lPts2[i][1]);
}
saveTable(data, 'pattern.csv');
}