Created
October 18, 2019 05:43
-
-
Save typicalTYLER/3fc7b37a76051bb29898fec83ff84472 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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