Skip to content

Instantly share code, notes, and snippets.

@typicalTYLER
Created October 18, 2019 05:43
Show Gist options
  • Save typicalTYLER/3fc7b37a76051bb29898fec83ff84472 to your computer and use it in GitHub Desktop.
Save typicalTYLER/3fc7b37a76051bb29898fec83ff84472 to your computer and use it in GitHub Desktop.
//http://www.edwardzajec.com/tvc4/pil1/index.html
const canvasSketch = require('canvas-sketch');
const { renderPaths, createPath, pathsToPolylines } = require('canvas-sketch-util/penplot');
const { clipPolylinesToBox } = require('canvas-sketch-util/geometry');
const Random = require('canvas-sketch-util/random');
// You can force a specific seed by replacing this with a string value
const defaultSeed = '';
const top_hatching_per_square = 5;
const side_hatching_per_square = 10;
const dimensions = 20;
const num_white_squares = random_gaussian_range(20, 30);
const expected_snake_length = 3;
const quadrangle_odds = random_gaussian_range(0, 1);
const max_quadrangle_side = Random.value(2, dimensions);
// Set a random seed so we can reproduce this print later
Random.setSeed(defaultSeed || Random.getRandomSeed());
// Print to console so we can see which seed is being used and copy it if desired
console.log('Random Seed:', Random.getSeed());
const settings = {
suffix: Random.getSeed(),
dimensions: 'letter',
orientation: 'portrait',
pixelsPerInch: 300,
scaleToView: true,
units: 'cm'
};
function render_tile(tile_code, paths, tile_box, matrix, i, j) {
var x0 = tile_box[0];
var y0 = tile_box[1];
var x1 = tile_box[2];
var y1 = tile_box[3];
var xlen = x1 - x0;
var ylen = y1 - y0;
var xmid = (x0 + x1)/2;
var ymid = (y0 + y1)/2;
var p;
switch (tile_code) {
case 1:
var left = in_bounds(matrix, i-1, j) && matrix[i-1][j] == 1;
var down = in_bounds(matrix, i, j+1) && matrix[i][j+1] == 1;
var right = in_bounds(matrix, i+1, j) && matrix[i+1][j] == 1;
var up = in_bounds(matrix, i, j-1) && matrix[i][j-1] == 1;
p = createPath();
p.moveTo(x0, y0);
if(left) {
p.moveTo(x0, y1);
} else {
p.lineTo(x0, y1);
}
if(down) {
p.moveTo(x1, y1);
} else {
p.lineTo(x1, y1);
}
if(right) {
p.moveTo(x1, y0);
} else {
p.lineTo(x1, y0);
}
if(up) {
p.moveTo(x0, y0);
} else {
p.lineTo(x0, y0);
}
paths.push(p);
break;
case 2:
p = createPath();
p.moveTo(x0, ymid);
p.lineTo(xmid, y0);
paths.push(p);
p = createPath();
p.moveTo(x0, y1);
p.lineTo(x1, y0);
paths.push(p);
for(var i = 1; i <= top_hatching_per_square; i++) {
p = createPath();
p.moveTo(x1, y0 + (ylen / top_hatching_per_square) * i);
p.lineTo(x0 + (top_hatching_per_square - i) * (xlen / top_hatching_per_square), y0 + (ylen / top_hatching_per_square) * i);
paths.push(p);
}
break;
case 3:
p = createPath();
p.moveTo(x0, y1);
p.lineTo(x1, y0);
paths.push(p);
for(var i = 1; i <= top_hatching_per_square; i++) {
p = createPath();
p.moveTo(x0, y1 - (ylen / top_hatching_per_square) * i);
p.lineTo(x1 - (top_hatching_per_square - i) * (xlen / top_hatching_per_square), y1 - (ylen / top_hatching_per_square) * i);
paths.push(p);
}
for(var i = 0; i < side_hatching_per_square; i++) {
p = createPath();
p.moveTo(x1 - i * (xlen / side_hatching_per_square), y0 + (ylen / side_hatching_per_square) * i);
p.lineTo(x1 - i * (xlen / side_hatching_per_square), y1);
paths.push(p);
}
break;
case 4:
p = createPath();
p.moveTo(xmid, y1);
p.lineTo(x1, ymid);
paths.push(p);
p = createPath();
p.moveTo(x0, y1);
p.lineTo(x1, y0);
paths.push(p);
for(var i = 1; i <= side_hatching_per_square; i++) {
p = createPath();
p.moveTo(x1 - i * (xlen / side_hatching_per_square), y0 + (ylen / side_hatching_per_square) * i);
p.lineTo(x1 - i * (xlen / side_hatching_per_square), y0);
paths.push(p);
}
break;
case 8:
for(var i = 1; i <= side_hatching_per_square; i++) {
p = createPath();
p.moveTo(x1 - i * (xlen / side_hatching_per_square), y0 + (ylen / side_hatching_per_square) * i);
p.lineTo(x1 - i * (xlen / side_hatching_per_square), y0);
paths.push(p);
}
p = createPath();
p.moveTo(x0, y1);
p.lineTo(x1, y0);
paths.push(p);
for(var i = 1; i <= top_hatching_per_square; i++) {
p = createPath();
p.moveTo(x1, y0 + (ylen / top_hatching_per_square) * i);
p.lineTo(x0 + (top_hatching_per_square - i) * (xlen / top_hatching_per_square), y0 + (ylen / top_hatching_per_square) * i);
paths.push(p);
}
break;
case 5:
for(var i = 0; i <= top_hatching_per_square; i++) {
p = createPath();
p.moveTo(x0, y0 + (ylen / top_hatching_per_square) * i);
p.lineTo(x1, y0 + (ylen / top_hatching_per_square) * i);
paths.push(p);
}
break;
case 6:
for(var i = 0; i <= side_hatching_per_square; i++) {
p = createPath();
p.moveTo(x0 + i * (xlen / side_hatching_per_square), y0);
p.lineTo(x0 + i * (xlen / side_hatching_per_square), y1);
paths.push(p);
}
break;
case 7:
p = createPath();
p.moveTo(xmid, y1);
p.lineTo(x1, ymid);
paths.push(p);
p = createPath();
p.moveTo(x0, y1);
p.lineTo(x1, y0);
paths.push(p);
p = createPath();
p.moveTo(x0, ymid);
p.lineTo(xmid, y0);
paths.push(p);
break;
}
}
function random_gaussian_range(start, stop) {
return Math.max(Math.min(Random.gaussian((start + stop) / 2, (stop - start) / 6), stop), start);
}
function init_matrix() {
var matrix = [];
for(var i=0; i<dimensions; i++) {
matrix[i] = [];
for(var j=0; j<dimensions; j++) {
matrix[i][j] = undefined;
}
}
return matrix;
}
function init_totally_random_matrix() {
var matrix = [];
for(var i=0; i<dimensions; i++) {
matrix[i] = [];
for(var j=0; j<dimensions; j++) {
matrix[i][j] = Math.floor(Random.range(1,9));
}
}
return matrix;
}
function find_random_empty_direction(matrix, i, j) {
var directions = [[1, 0], [0, 1], [-1, 0], [0, 1]];
directions = Random.shuffle(directions);
while (directions.length > 0) {
var direction = directions.pop();
var i_new = i + direction[0];
var j_new = j + direction[1];
if (in_bounds(matrix, i_new, j_new) && matrix[i_new][j_new] != 1) {
return direction;
}
}
return false;
}
function get_quadrangle_box(matrix, remaining_white_squares) {
var i_length = Math.round(random_gaussian_range(2, Math.min(max_quadrangle_side, matrix.length - 1, remaining_white_squares)));
var j_length = Math.round(random_gaussian_range(2, Math.min(max_quadrangle_side, matrix[0].length - 1, (remaining_white_squares / i_length))));
var i_pos = Math.round(random_gaussian_range(0, (matrix.length - 1) - i_length));
var j_pos = Math.round(random_gaussian_range(0, (matrix[0].length - 1) - j_length));
return [i_pos, j_pos, i_pos + i_length, j_pos + j_length];
}
function il_cubo_special_white_square_placement(matrix) {
var remaining_white_squares = num_white_squares;
while (remaining_white_squares > 0) {
if (Random.value() > quadrangle_odds || remaining_white_squares < 4) { // start snake
var i, j;
do {
i = Math.round(random_gaussian_range(0, matrix.length - 1));
j = Math.round(random_gaussian_range(0, matrix[i].length - 1));
}
while (matrix[i][j] == 1);
matrix[i][j] = 1;
remaining_white_squares--;
var snake_length = random_gaussian_range(1 , expected_snake_length * 2) - 1;
for (var k = 1; k < snake_length && remaining_white_squares > 0; k++) {
var direction = find_random_empty_direction(matrix, i, j);
if (!direction) {
break;
}
i += direction[0];
j += direction[1];
matrix[i][j] = 1;
remaining_white_squares--;
}
} else { // start quadrangle
var box = get_quadrangle_box(matrix, remaining_white_squares);
for (var i = box[0]; i < box[2]; i++) {
for (var j = box[1]; j < box[3]; j++) {
if (matrix[i][j] != 1) {
matrix[i][j] = 1;
remaining_white_squares--;
}
}
}
}
}
return build_cubo_matrix_from_white_squares(matrix);
}
function uniform_random_white_square_placement(matrix) {
var remaining_white_squares = num_white_squares;
while (remaining_white_squares > 0) {
var i = Math.floor(Random.range(dimensions));
var j = Math.floor(Random.range(dimensions));
if(matrix[i][j] == undefined) {
matrix[i][j] = 1;
remaining_white_squares--;
}
}
return build_cubo_matrix_from_white_squares(matrix)
}
function in_bounds(matrix, i, j) {
return i >= 0 && j >= 0 && i < matrix.length && j < matrix[i].length;
}
//this part was confusing to implement, i had to draw a lot of diagrams
function tile_for_position(matrix, i, j) {
if (matrix[i][j] == 1) {
return 1;
}
var left = in_bounds(matrix, i-1, j) && matrix[i-1][j] == 1;
var down = in_bounds(matrix, i, j+1) && matrix[i][j+1] == 1;
var diag1 = in_bounds(matrix, i-1, j+1) && matrix[i-1][j+1] == 1;
var diag1left = in_bounds(matrix, i-2, j+1) && matrix[i-2][j+1] == 1;
var diag1down = in_bounds(matrix, i-1, j+2) && matrix[i-1][j+2] == 1;
var diag2 = in_bounds(matrix, i-2, j+2) && matrix[i-2][j+2] == 1;
var top_left_pattern = 0;
var bottom_right_pattern = 0;
if(left) {
top_left_pattern = 2;
} else if (diag1) {
top_left_pattern = 1;
} else if (diag1left) {
top_left_pattern = 2;
} else if (diag2) {
top_left_pattern = 1;
}
if(down) {
bottom_right_pattern = 1;
} else if (diag1) {
bottom_right_pattern = 2;
} else if (diag1down) {
bottom_right_pattern = 1;
} else if (diag2) {
bottom_right_pattern = 2;
}
if(top_left_pattern == 0 && bottom_right_pattern == 0) {
return 7;
} else if(top_left_pattern == 0 && bottom_right_pattern == 1) {
return 2;
} else if(top_left_pattern == 0 && bottom_right_pattern == 2) {
return 0; //doesn't get used
} else if(top_left_pattern == 1 && bottom_right_pattern == 0) {
return 0; //doesn't get used
} else if(top_left_pattern == 1 && bottom_right_pattern == 1) {
return 5;
} else if(top_left_pattern == 1 && bottom_right_pattern == 2) {
return 3;
} else if(top_left_pattern == 2 && bottom_right_pattern == 0) {
return 4;
} else if(top_left_pattern == 2 && bottom_right_pattern == 1) {
return 8;
} else if(top_left_pattern == 2 && bottom_right_pattern == 2) {
return 6;
}
}
function build_cubo_matrix_from_white_squares(matrix) {
for(var i=0; i<matrix.length; i++) {
for(var j=0; j<matrix[i].length; j++) {
matrix[i][j] = tile_for_position(matrix, i, j);
}
}
return matrix;
}
function render_matrix(matrix, paths, box) {
// assume square (it is il cubo after all)
var side_length = (box[2] - box[0]) / matrix.length;
for(var i=0; i<matrix.length; i++) {
for(var j=0; j<matrix[i].length; j++) {
var tile_box = [box[0] + i * side_length, box[1] + j * side_length,
box[0] + (i+1) * side_length, box[1] + (j+1) * side_length];
render_tile(matrix[i][j], paths, tile_box, matrix, i, j);
}
}
var x0 = box[0];
var y0 = box[1];
var x1 = box[2];
var y1 = box[3];
var p = createPath();
p.moveTo(x0, y0);
p.lineTo(x0, y1);
p.lineTo(x1, y1);
p.lineTo(x1, y0);
p.lineTo(x0, y0);
paths.push(p);
}
const sketch = (props) => {
const { width, height, units } = props;
// Holds all our 'path' objects
// which could be from createPath, or SVGPath string, or polylines
const paths = [];
var matrix = init_totally_random_matrix();
matrix = il_cubo_special_white_square_placement(init_matrix());
const edgeLength = Math.min(width, height) * 2 / 3;
const box = [ (width - edgeLength) / 2, (height - edgeLength) / 2,
((width - edgeLength) / 2) + edgeLength,
((height - edgeLength) / 2) + edgeLength ];
render_matrix(matrix, paths, box);
// Convert the paths into polylines so we can apply line-clipping
// When converting, pass the 'units' to get a nice default curve resolution
let lines = pathsToPolylines(paths, { units });
// Clip to bounds, using a margin in working units
// const margin = 1; // in working 'units' based on settings
// const box = [ margin, margin, width - margin, height - margin ];
// lines = clipPolylinesToBox(lines, box);
// The 'penplot' util includes a utility to render
// and export both PNG and SVG files
return props => renderPaths(lines, {
...props,
lineJoin: 'round',
lineCap: 'round',
// in working units; you might have a thicker pen
lineWidth: 0.025,
// Optimize SVG paths for pen plotter use
optimize: {
sort : true,
removeDuplicates : true,
removeCollinear : true,
merge : true,
mergeThreshold : 0.06
}
});
};
canvasSketch(sketch, settings);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment