xxxxxxxxxx
632
// 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 = 25; // 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;
function setup() {
createCanvas(400, 400);
strokeWeight(3);
noFill();
strokeCap(SQUARE);
angleMode(DEGREES);
slider = createSlider(1, 20, 1);
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() {
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 full horizontal or vertical lines
// method 1: draw a line from bottom left corner through some points, bounce when it hits a normal wall and end when it hits the left wall, it should touch or cross the diagonal at least once (r2) -> do not go left until the diagonal is hit
// then mirror over diagonal(r1), derive tiles from intersections.
generatePath();
print('points on line: ' + lPts.length);
findIntersections();
print('segments: ' + segs.length);
calculatePolygons();
}
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);
}
// DRAW FUNCTIONS
function draw() {
background(0);
// drawConstructionPoints();
// drawSegments(segs);
stroke(255);
noFill();
rect(100,100,width/2,height/2)
noStroke()
for(let i=0;i<polygons.length;i++){
fill(noise(polygons[i].length)*255,noise(polygons[i].length+20)*255,noise(polygons[i].length+40)*255,120)
beginShape()
for(let j=0;j<polygons[i].length;j++){
vertex(polygons[i][j][0] * width / 200 + 100, polygons[i][j][1] * height / 200 + 100)
}
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');
}