Skip to content

Instantly share code, notes, and snippets.

@LABCAT
Forked from dribnet/.block
Last active October 18, 2017 05:23
Show Gist options
  • Select an option

  • Save LABCAT/4c4938558bbac696976deaff221f75a1 to your computer and use it in GitHub Desktop.

Select an option

Save LABCAT/4c4938558bbac696976deaff221f75a1 to your computer and use it in GitHub Desktop.
17.2.MDDN342 PS4
license: mit
height: 720
function resetFocusedRandom() {
return Math.seedrandom(arguments);
}
function focusedRandom(min, max, focus, mean) {
// console.log("hello")
if(max === undefined) {
max = min;
min = 0;
}
if(focus === undefined) {
focus = 1.0;
}
if(mean === undefined) {
mean = (min + max) / 2.0;
}
if(focus == 0) {
return d3.randomUniform(min, max)();
}
else if(focus < 0) {
focus = -1 / focus;
}
sigma = (max - mean) / focus;
val = d3.randomNormal(mean, sigma)();
if (val > min && val < max) {
return val;
}
return d3.randomUniform(min, max)();
}

Rainbow Spiral Map

The rainbow spiral map is a gigantic diamond shape consisting of approximately 1.5 million octagon zones. Each octagon zone is placed on the map in a specific order with the first zone located slightly to the left of the maps center. Every following zone is placed on the map based on the location of the zone that precedes it.

The colour palette of the map uses the HSB colour mode. Beginning from the first octagon zone, there is a repeating pattern consisting of 24 colours used to determine the colour of each zone. This creates the rainbow spiral effect present at the highest zoom level.

Each octagon zone also consists of a set of 24 glyphs. At the higher zoom levels the glyphs consist of a simple shape. This shape is chosen using the perlin noise value obtained from the x and y co-ordinates of the glyphs center. As you zoom into the map the glyphs start transforming into intricate patterns made by rotating the shape around it's center.

The octagon zones have been designed to provide an interesting experience when using the zooming tool. If you start from the center of a zone at the highest level and then zoom into the deepest level, you will see the glyphs evolve as you travel through each layer. As you get closer to the deepest level, a secret glyph becomes visible at the very center of the zone. This glyph represents the primary colour of the zone and is unique for each of the 24 possible colours.

var debug = true;
var blue = "#0000AA";
var light_blue = "#0088FF";
function drawGrid(p5, x1, x2, y1, y2, z, zoom) {
p5.noiseDetail(8,0.5);
p5.background('#000000');
p5.stroke(light_blue);
drawLayer(p5, 16, x1, x2, y1, y2, z);
}
function drawLayer(p5, slashsize, x1, x2, y1, y2, z, zoom) {
var noiseScale=1/16.0;
var startx = slashsize * Math.floor(x1/slashsize);
var starty = slashsize * Math.floor(y1/slashsize);
var endx = slashsize * (Math.floor(x2/slashsize));
var endy = slashsize * (Math.floor(y2/slashsize));
var char_width = 256 / ((x2-x1)/slashsize);
var char_height = 256 / ((y2-y1)/slashsize);
var pixel_width = char_width / slashsize;
var pixel_height = char_height / slashsize;
p5.strokeWeight(pixel_width);
p5.rectMode(p5.CENTER);
var columnPointer = 0;
var rowPointer = 0;
var coordsCount = 0;
var coOrds = [];
for(var x=startx; x<endx; x+=slashsize) {
coOrds[columnPointer] = [];
var n_x = x / slashsize;
var x_pos = p5.map(x, x1, x2, 0, 256);
for(var y=starty; y<endy; y+=slashsize) {
var n_y = y / slashsize;
var y_pos = p5.map(y, y1, y2, 0, 256);
var noiseValue = p5.noise(x * noiseScale, y * noiseScale, z);
var shapeSize = char_width/2;
if (noiseValue < 0.2) {
p5.ellipse(x_pos+(char_width/2), y_pos+(char_width/2), shapeSize, shapeSize);
}
else if (noiseValue < 0.4) {
p5.rect(x_pos+(char_width/2), y_pos+(char_width/2), shapeSize, shapeSize);
}
else if (noiseValue < 0.6) {
octa(p5, x_pos+(char_width/2), y_pos+(char_width/2), shapeSize);
}
else if (noiseValue < 0.8) {
equilateral(p5, x_pos+(char_width/2), y_pos+(char_width/2), shapeSize);
}
else {
hexa(p5, x_pos+(char_width/2), y_pos+(char_width/2), shapeSize);
}
coOrds[columnPointer][rowPointer] = [x_pos+(char_width/2), y_pos+(char_width/2)];
coordsCount++;
rowPointer++;
}
rowPointer = 0;
columnPointer++;
}
//draw the larger hexagons
if(coordsCount >= 16){
var numOfHexLoops = columnPointer/4;
var colStart = 1, colEnd = 4;
for(var colLoop=0; colLoop < numOfHexLoops; colLoop++){
var rowStart = 1, rowEnd = 4;
for(var rowLoop=0; rowLoop < numOfHexLoops; rowLoop++){
var hexCoOrds = [];
for(var col=colStart; col <= colEnd; col++){
for(var row=rowStart; row <= rowEnd; row++){
if ((col % 4) == 1){
if ((row % 4) == 3){
hexCoOrds[0] = coOrds[col-1][row-1];
}
else if ((row % 4) == 2){
hexCoOrds[1] = coOrds[col-1][row-1];
}
}
else if ((col % 4) == 2){
if ((row % 4) == 1){
hexCoOrds[2] = coOrds[col-1][row-1];
}
else if ((row % 4) == 0){
hexCoOrds[7] = coOrds[col-1][row-1];
}
}
else if ((col % 4) == 3){
if ((row % 4) == 1){
hexCoOrds[3] = coOrds[col-1][row-1];
}
else if ((row % 4) == 0){
hexCoOrds[6] = coOrds[col-1][row-1];
}
}
else if ((col % 4) == 0){
if ((row % 4) == 2){
hexCoOrds[4] = coOrds[col-1][row-1];
}
else if ((row % 4) == 3){
hexCoOrds[5] = coOrds[col-1][row-1];
}
}
}
}
rowStart = rowStart + 4;
rowEnd = rowEnd + 4;
drawHexOutline(p5, hexCoOrds, pixel_width);
}
colStart = colStart + 4;
colEnd = colEnd + 4;
}
}
if(debug){
// debug - show border
p5.noFill();
p5.stroke(255, 0, 0);
p5.rectMode(p5.CORNER);
p5.rect(0, 0, 255, 255);
}
}
//array of hue ranges used for selecting colour of the hex outline
var hueRanges = [
[-22.5, 22.5, 0],
[22.5, 67.5, 45],
[67.5, 112.5, 90],
[112.5, 157.5, 135],
[157.5, 202.5, 180],
[202.5, 247.5, 225],
[247.5, 292.5, 270],
[292.5, 337.5, 315],
];
//draws the lines that connects the hexagons together
function drawHexOutline(p5, coOrds, weight) {
var rand = Math.floor(p5.random(0,8));
var h = focusedRandom(hueRanges[rand][0], hueRanges[rand][1], 10, hueRanges[rand][2]);
p5.colorMode(p5.HSB);
p5.stroke(h, 100, 100);
p5.strokeWeight(weight * 2);
p5.noFill();
p5.beginShape();
for (var i = 0; i < coOrds.length; i++) {
var xPos = coOrds[i][0];
var yPos = coOrds[i][1];
p5.vertex(xPos, yPos);
}
p5.endShape(p5.CLOSE);
p5.colorMode(p5.RGB);
}
/*
* function to draw an equilateral triangle with a set width
* based on x, y co-oridinates that are the center of the triangle
* @param {Number} x - x-coordinate that is at the center of triangle
* @param {Number} y - y-coordinate that is at the center of triangle
* @param {Number} width - radius of the hexagon
*/
function equilateral(p5, x, y, width) {
var x1 = x - (width/2);
var y1 = y + (width/2);
var x2 = x;
var y2 = y - (width/2);
var x3 = x + (width/2);
var y3 = y + (width/2);
p5.triangle(x1,y1,x2,y2,x3,y3);
}
/*
* function to draw a hexagon shape
* adapted from: https://p5js.org/examples/form-regular-polygon.html
* @param {Number} x - x-coordinate of the hexagon
* @param {Number} y - y-coordinate of the hexagon
* @param {Number} radius - radius of the hexagon
*/
function hexa(p5, x, y, radius) {
radius = radius / 2;
p5.angleMode(p5.RADIANS);
var angle = p5.TWO_PI / 6;
p5.beginShape();
for (var a = p5.TWO_PI/12; a < p5.TWO_PI + p5.TWO_PI/12; a += angle) {
var sx = x + p5.cos(a) * radius;
var sy = y + p5.sin(a) * radius;
p5.vertex(sx, sy);
}
p5.endShape(p5.CLOSE);
}
/*
* function to draw a octagon shape
* adapted from: https://p5js.org/examples/form-regular-polygon.html
* @param {Number} x - x-coordinate of the octagon
* @param {Number} y - y-coordinate of the octagon
* @param {Number} radius - radius of the octagon
*/
function octa(p5, x, y, radius) {
radius = radius / 2;
p5.angleMode(p5.RADIANS);
var angle = p5.TWO_PI / 8;
p5.beginShape();
for (var a = p5.TWO_PI/16; a < p5.TWO_PI + p5.TWO_PI/16; a += angle) {
var sx = x + p5.cos(a) * radius;
var sy = y + p5.sin(a) * radius;
p5.vertex(sx, sy);
}
p5.endShape(p5.CLOSE);
}
/* This is an example of 10print translated into the PS4 framework */
var blue = "#0000AA"
var light_blue = "#0088FF"
function drawLayer(p5, slashsize, x1, x2, y1, y2, z) {
var noiseScale=1/16.0;
var startx = slashsize * (Math.floor(x1/slashsize) - 1);
var starty = slashsize * (Math.floor(y1/slashsize) - 1);
var endx = slashsize * (Math.floor(x2/slashsize) + 1);
var endy = slashsize * (Math.floor(y2/slashsize) + 1);
var char_width = 256 / ((x2-x1)/slashsize);
var char_height = 256 / ((y2-y1)/slashsize);
var pixel_width = char_width / 8;
var pixel_height = char_height / 8;
p5.strokeWeight(pixel_width);
for(var x=startx; x<endx; x+=slashsize) {
var n_x = x / slashsize;
var x_pos = p5.map(x, x1, x2, 0, 256);
for(var y=starty; y<endy; y+=slashsize) {
var n_y = y / slashsize;
var y_pos = p5.map(y, y1, y2, 0, 256);
var noiseValue = p5.noise(x * noiseScale, y * noiseScale, z);
if (noiseValue < 0.5) {
p5.line(x_pos, y_pos, x_pos+char_width, y_pos+char_height);
}
else {
p5.line(x_pos, y_pos+char_height, x_pos+char_width, y_pos);
}
}
}
}
function drawGrid(p5, x1, x2, y1, y2, z, zoom) {
p5.noiseDetail(8,0.5);
p5.background(blue);
p5.stroke(light_blue);
drawLayer(p5, 16, x1, x2, y1, y2, z);
}
var max_thickness = 64;
var max_movement = 32;
var ball_radius = 32;
var line_width = 8;
var grid_size = 64;
/* the random number seed for the tour */
var tourSeed = 301;
/* triplets of locations: zoom, x, y */
var tourPath = [
[1, 356.500000000000, 665.750000000000],
[3, 353.250000000000, 668.187500000000],
[4, 322.562500000000, 645.093750000000],
[5, 322.562500000000, 645.109375000000],
[7, 317.984375000000, 643.636718750000],
[3, 317.984375000000, 643.636718750000]
]
function getOffsetPoint(p5, x, y, z, noiseScale) {
var noiseX = p5.noise(x * noiseScale,
y * noiseScale, z);
var noiseY = p5.noise(x * noiseScale,
y * noiseScale, z+50);
var offsetX = p5.map(noiseX, 0, 1, -max_movement, max_movement);
var offsetY = p5.map(noiseY, 0, 1, -max_movement, max_movement);
return [x+offsetX, y+offsetY]
}
function snap_to_grid(num, gsize) {
return (num - (num % gsize));
}
function drawPetals(p5, x1, x2, y1, y2, pos_x, pos_y, rad1, rad2) {
var offsets = [
[0, 1],
[1, 0],
[0, -1],
[-1, 0]
]
var pixel_posx1 = p5.map(pos_x, x1, x2, 0, 256);
var pixel_posx2 = p5.map(pos_x+rad2, x1, x2, 0, 256);
var pixel_radius = pixel_posx2 - pixel_posx1;
for(var i=0; i<offsets.length; i++) {
var offset = offsets[i];
var pixel_x = p5.map(pos_x+0.5*rad1*offset[0], x1, x2, 0, 256);
var pixel_y = p5.map(pos_y+0.5*rad1*offset[1], y1, y2, 0, 256);
p5.ellipse(pixel_x, pixel_y, pixel_radius);
}
}
function drawStamens(p5, x1, x2, y1, y2, pos_x, pos_y, rad1, rad2, drawLines) {
var offsets = [
[1, 1],
[1, -1],
[-1, 1],
[-1, -1]
]
var pixel_posx1 = p5.map(pos_x, x1, x2, 0, 256);
var pixel_posx2 = p5.map(pos_x+rad2, x1, x2, 0, 256);
var pixel_radius = pixel_posx2 - pixel_posx1;
for(var i=0; i<offsets.length; i++) {
var offset = offsets[i];
var pixel_x = p5.map(pos_x+0.5*rad1*offset[0], x1, x2, 0, 256);
var pixel_y = p5.map(pos_y+0.5*rad1*offset[1], y1, y2, 0, 256);
p5.strokeWeight(0);
p5.ellipse(pixel_x, pixel_y, pixel_radius);
if(drawLines) {
p5.strokeWeight(pixel_radius / 20);
p5.line(pixel_x-pixel_radius, pixel_y, pixel_x+pixel_radius, pixel_y);
p5.line(pixel_x, pixel_y-pixel_radius, pixel_x, pixel_y+pixel_radius);
p5.strokeWeight(0);
p5.ellipse(pixel_x, pixel_y, pixel_radius / 12);
}
}
}
/*
* This is the funciton to implement to make your own abstract design.
*
* arguments:
* p5: the p5.js object - all draw commands should be prefixed with this object
* x1, x2, y1, y2: draw the pattern contained in the rectangle x1,y1 to x2, y2
* z: use this as the noise z offset (can be shifted)
* zoom: current zoom level (starts at 0), useful to decide how much detail to draw
*
* The destination drawing should be in the square 0, 0, 255, 255.
*/
function drawGrid(p5, x1, x2, y1, y2, z, zoom) {
// debug - show border
var max_shift = max_thickness + max_movement;
var min_x = snap_to_grid(x1 - max_shift, grid_size);
var max_x = snap_to_grid(x2 + max_shift + grid_size, grid_size);
var min_y = snap_to_grid(y1 - max_shift, grid_size);
var max_y = snap_to_grid(y2 + max_shift + grid_size, grid_size);
var c_p00 = p5.map(0, x1, x2, 0, 256);
var c_plwidth = p5.map(line_width, x1, x2, 0, 256);
var c_pball = p5.map(ball_radius, x1, x2, 0, 256);
var cur_line_width = c_plwidth - c_p00;
var cur_ball_radius = c_pball - c_p00;
p5.background(255);
for(var x=min_x; x<max_x; x+=grid_size) {
for(var y=min_y; y<max_y; y+=grid_size) {
var shift_point = getOffsetPoint(p5, x, y, z, 0.1);
var x_pos = p5.map(shift_point[0], x1, x2, 0, 256);
var y_pos = p5.map(shift_point[1], y1, y2, 0, 256);
p5.strokeWeight(cur_line_width);
p5.stroke(0, 128, 0);
var shift_point2 = getOffsetPoint(p5, x+grid_size, y, z, 0.1);
var x_pos2 = p5.map(shift_point2[0], x1, x2, 0, 256);
var y_pos2 = p5.map(shift_point2[1], y1, y2, 0, 256);
p5.line(x_pos, y_pos, x_pos2, y_pos2);
var shift_point2 = getOffsetPoint(p5, x, y+grid_size, z, 0.1);
var x_pos2 = p5.map(shift_point2[0], x1, x2, 0, 256);
var y_pos2 = p5.map(shift_point2[1], y1, y2, 0, 256);
p5.line(x_pos, y_pos, x_pos2, y_pos2);
p5.noStroke();
// if zoomed: first, draw petals *behind* the ellipse
if(zoom >= 3) {
p5.fill(0, 0, 255);
drawPetals(p5, x1, x2, y1, y2, shift_point[0], shift_point[1], ball_radius, line_width);
}
// draw ellipse
p5.fill(0, 0, 128);
p5.ellipse(x_pos, y_pos, cur_ball_radius);
// if zoomed: last draw stamens *in front of* the ellipse
if(zoom >= 3) {
// now if we are super zoomed, draw lines in the stamen
var drawLines = false;
if (zoom >= 5) drawLines = true;
p5.fill(0, 0, 255);
p5.stroke(0, 0, 128);
drawStamens(p5, x1, x2, y1, y2, shift_point[0], shift_point[1], ball_radius/3, line_width/2, drawLines);
}
}
}
}
/*
* This is the funciton to implement to make your own abstract design.
*
* arguments:
* p5: the p5.js object - all draw commands should be prefixed with this object
* x1, x2, y1, y2: draw the pattern contained in the rectangle x1,y1 to x2, y2
* z: use this as the noise z offset (can be shifted)
* zoom: current zoom level (starts at 0), useful to decide how much detail to draw
*
* The destination drawing should be in the square 0, 0, 255, 255.
*/
// This version draws two rectangles and two ellipses.
// The rectangles are 960x720 and centered at 512,512.
function drawGrid(p5, x1, x2, y1, y2, z, zoom) {
console.log(y1, y2);
p5.background(255);
p5.rectMode(p5.CORNERS);
// The first red rectangle fills the entire space
var cx = p5.map(512-960/2, x1, x2, 0, 256);
var cy = p5.map(512-720/2, y1, y2, 0, 256);
var cx2 = p5.map(512+960/2, x1, x2, 0, 256);
var cy2 = p5.map(512+720/2, y1, y2, 0, 256);
p5.fill(255, 0, 0);
p5.rect(cx, cy, cx2, cy2);
// The second black rectangle is inset to form a frame inset by 20 units
cx = p5.map(512-940/2, x1, x2, 0, 256);
cy = p5.map(512-700/2, y1, y2, 0, 256);
cx2 = p5.map(512+940/2, x1, x2, 0, 256);
cy2 = p5.map(512+700/2, y1, y2, 0, 256);
p5.fill(0);
p5.rect(cx, cy, cx2, cy2);
// Two ellipses with a radius of 50 units are then added.
var cx = p5.map(512, x1, x2, 0, 256);
var cy = p5.map(512, y1, y2, 0, 256);
var cx2 = p5.map(512+50, x1, x2, 0, 256);
p5.fill(0, 0, 255);
p5.ellipse(cx, cy, (cx2-cx));
// The second green ellipse is above and to the left of the first one.
var cx = p5.map(412, x1, x2, 0, 256);
var cy = p5.map(412, y1, y2, 0, 256);
var cx2 = p5.map(412+50, x1, x2, 0, 256);
p5.fill(0, 255, 0);
p5.ellipse(cx, cy, (cx2-cx));
}
/*
* This is the funciton to implement to make your own abstract design.
*
* arguments:
* p5: the p5.js object - all draw commands should be prefixed with this object
* x1, x2, y1, y2: draw the pattern contained in the rectangle x1,y1 to x2, y2
* z: use this as the noise z offset (can be shifted)
* zoom: current zoom level (starts at 0), useful to decide how much detail to draw
*
* The destination drawing should be in the square 0, 0, 255, 255.
*/
/* TOUR VARIABLES (required)
/* the random number seed for the tour */
var tourSeed = 100;
/* triplets of locations: zoom, x, y */
var tourPath = [
[5, 510, 507],
[7, 507, 506],
[3, 512, 515],
[1, 512, 512]
]
/* OPTIONAL VARIABLES */
/* what is the initial zoom level (defaults to 0) */
var initialZoomLevel = 3;
/* what is the maximum zoom level (make this at least 10. defaults to 16) */
var maxZoomLevel = 10;
function drawPetals(p5, x1, x2, y1, y2, pos_x, pos_y, rad1, rad2) {
var offsets = [
[0, 1],
[1, 0],
[0, -1],
[-1, 0]
]
var pixel_posx1 = p5.map(pos_x, x1, x2, 0, 256);
var pixel_posx2 = p5.map(pos_x+rad2, x1, x2, 0, 256);
var pixel_radius = pixel_posx2 - pixel_posx1;
for(var i=0; i<offsets.length; i++) {
var offset = offsets[i];
var pixel_x = p5.map(pos_x+0.5*rad1*offset[0], x1, x2, 0, 256);
var pixel_y = p5.map(pos_y+0.5*rad1*offset[1], y1, y2, 0, 256);
p5.ellipse(pixel_x, pixel_y, pixel_radius);
}
}
function drawStamens(p5, x1, x2, y1, y2, pos_x, pos_y, rad1, rad2, drawLines) {
var offsets = [
[1, 1],
[1, -1],
[-1, 1],
[-1, -1]
]
var pixel_posx1 = p5.map(pos_x, x1, x2, 0, 256);
var pixel_posx2 = p5.map(pos_x+rad2, x1, x2, 0, 256);
var pixel_radius = pixel_posx2 - pixel_posx1;
for(var i=0; i<offsets.length; i++) {
var offset = offsets[i];
var pixel_x = p5.map(pos_x+0.5*rad1*offset[0], x1, x2, 0, 256);
var pixel_y = p5.map(pos_y+0.5*rad1*offset[1], y1, y2, 0, 256);
p5.strokeWeight(0);
p5.ellipse(pixel_x, pixel_y, pixel_radius);
if(drawLines) {
p5.strokeWeight(pixel_radius / 20);
p5.line(pixel_x-pixel_radius, pixel_y, pixel_x+pixel_radius, pixel_y);
p5.line(pixel_x, pixel_y-pixel_radius, pixel_x, pixel_y+pixel_radius);
p5.strokeWeight(0);
p5.ellipse(pixel_x, pixel_y, pixel_radius / 12);
}
}
}
// This version draws two rectangles and two ellipses.
// The rectangles are 960x720 and centered at 512,512.
function drawGrid(p5, x1, x2, y1, y2, z, zoom) {
p5.background(255);
p5.rectMode(p5.CORNERS);
// The first red rectangle fills the entire space
var cx = p5.map(512-960/2, x1, x2, 0, 256);
var cy = p5.map(512-720/2, y1, y2, 0, 256);
var cx2 = p5.map(512+960/2, x1, x2, 0, 256);
var cy2 = p5.map(512+720/2, y1, y2, 0, 256);
p5.fill(255, 0, 0);
p5.rect(cx, cy, cx2, cy2);
// The second black rectangle is inset to form a frame inset by 20 units
cx = p5.map(512-940/2, x1, x2, 0, 256);
cy = p5.map(512-700/2, y1, y2, 0, 256);
cx2 = p5.map(512+940/2, x1, x2, 0, 256);
cy2 = p5.map(512+700/2, y1, y2, 0, 256);
p5.fill(0);
p5.rect(cx, cy, cx2, cy2);
// Two ellipses with a radius of 50 units are then added.
// start with the center dot --
// if zoomed: first, draw petals *behind* the ellipse
if(zoom >= 3) {
p5.fill(0, 128, 128);
drawPetals(p5, x1, x2, y1, y2, 512, 512, 50, 10);
}
// first, draw a blue
var cx = p5.map(512, x1, x2, 0, 256);
var cy = p5.map(512, y1, y2, 0, 256);
var cx2 = p5.map(512+50, x1, x2, 0, 256);
p5.fill(0, 0, 255);
p5.ellipse(cx, cy, (cx2-cx));
// if zoomed: last draw stamens *in front of* the ellipse
if(zoom >= 3) {
// now if we are super zoomed, draw lines in the stamen
var drawLines = false;
if (zoom >= 5) drawLines = true;
p5.fill(0, 128, 128);
p5.stroke(0, 0, 255);
drawStamens(p5, x1, x2, y1, y2, 512, 512, 10, 8, drawLines);
}
// The second green ellipse is above and to the left of the first one.
var cx = p5.map(412, x1, x2, 0, 256);
var cy = p5.map(412, y1, y2, 0, 256);
var cx2 = p5.map(412+50, x1, x2, 0, 256);
p5.fill(0, 255, 0);
p5.ellipse(cx, cy, (cx2-cx));
}
function drawGrid(p5, x1, x2, y1, y2, z, zoom) {
var noiseScale=0.02;
p5.noiseDetail(8,0.5);
p5.noStroke();
for(var i=0; i<16; i++) {
var n_x = p5.map(i, 0, 16, x1, x2);
for(var j=0; j<16; j++) {
var n_y = p5.map(j, 0, 16, y1, y2);
var noiseVal = p5.noise(n_x * noiseScale,
n_y * noiseScale, z);
p5.fill(noiseVal*255);
p5.rect(i*16, j*16, 16, 16);
}
}
// debug - show border
// p5.noFill();
// p5.stroke(255, 0, 0)
// p5.rect(0, 0, 255, 255);
}
<!DOCTYPE html>
<meta charset="utf-8">
<title>framed version</title>
<div class="column">
<h3>960x720 preview</h3>
<div class="index">
<iframe sandbox="allow-popups allow-scripts allow-forms allow-same-origin" src="index.html" marginwidth="0" marginheight="0" style="width:960px;height:720px;" scrolling="no"></iframe>
</div>
</div>
/* This is based on the code I created for PS2 in MDDN242:
* http://www.purview.nz/versions/a14e8ec6660813fd3db1bb6525500e42.html
* I have made minor modifications to the code in order to allow it to work
* in co-orditaion with map.js and sketch.js
*/
function GrayGlyph() {
/*
* values is an array of 3 numbers: [hue, saturation, brightness]
* + hue ranges from 0..360
* + saturation ranges from 0..100
* + brightness ranges from 0..100
* this matches the default range for p5.js colorMode(HSB) as describe at:
* https://p5js.org/reference/#/p5/colorMode
*
* size is the number of pixels for width and height
*
* use p5.js to draw a round grayscale glpyh
* the glyph should stay within the bounding box [0, 0, width, height]
* this is a grayscale glyph, so only brighness can be adjusted.
* the range for brighness is 0..100
*
* the color mode will be HSB, so you can either:
* + use a one argument grayscale call; this is easiest. examples:
* - fill(50); // ranges from 0-100
* - stroke(100); // white
* + use a three arguement HSB call with values but set both H and S to 0. examples:
* - fill(0, 0, 51); // equivalent to above
* - stroke(0, 0, 100); //
*/
/*
* @param {Object} p5 - the p5.js object
* @param {Number} centerX - center of the x-axis for the current octagon zone
* @param {Number} centerY - center of the y-axis for the current octagon zone
* @param {Number} x1 - left side of a map tile
* @param {Number} x2 - right side of a map tile
* @param {Number} y1 - top side of a map tile
* @param {Number} y2 - bottom side of a map tile
* @param {Number} spot_hue - value of the hue - a number between 0 and 359
* @param {Number} spotMin - minimum hue value that determines whether or not to use the spot_hue - a number between 0 and 359
* @param {Number} spotMax - maximum hue value that determines whether or not to use the spot_hue - a number between 0 and 359
*/
this.draw = function(values, size, p5, centerX, centerY, x1, x2, y1, y2, spot_hue = 360, spotMin = 0, spotMax = 0) {
//determine the center of the circle
var center = size/2;
//this translation required to make the variable center the actual center of an octagonZone object
var cx = p5.map(centerX, x1, x2, 0, 256);
var cy = p5.map(centerY, y1, y2, 0, 256);
p5.translate(cx - center, cy - center);
//value of the hue dimension
var hueDegree = Math.floor(values[0]) % 360;
//horiVertMin and horiVertMax are values that determine the positions for the alternating points (between the center of a circle and its edge)
//of the vertical and horizontal triangles used to represent the hue dimension
var horiVertMin = p5.map(hueDegree, 0, 179, center, 0);
var horiVertMax = p5.map(hueDegree, 0, 179, center, size);
if(hueDegree > 179) {
horiVertMin = 0;
horiVertMax = size;
}
//diagonalMin and diagonalMax are values that determine the position for the alternating points (between the center of a circle and its edge)
//of the diagonal triangles representing the hue dimension
var diagonalMin = p5.map(hueDegree, 359, 180, (0 + size/8 + size/32), center);
var diagonalMax = p5.map(hueDegree, 359, 180, (size - size/8 - size/32), center);
//variables created by the saturation dimension
var circleSize = p5.map(values[1], 100, 0, size, 0 + size/16);
//JSON object containing the different values for the three circles drawn to represent the saturation dimension
var satCircles = {
'brightness' : [
0,
100,
0
],
'alpha' : [
0.1875,
0.625,
0.375
],
'size' : [
circleSize,
circleSize/2,
circleSize/4,
]
}
//variables created by the brightness dimension
var brightnessTrans = p5.map(values[2], 0, 100, 0.9, 0);
var hueColour = p5.map(values[2], 0, 100, 100, 0);
var hueTrans = p5.map(values[2], 100, 0, 0.9, 0.1);
//set up the JSON objects used to store all the x and y positions of the triangles that will be drawn when this is passed to the drawStar function
var positions = {
'x1': [
center - size/32,
center - size/32,
center,
center + size/32,
center - size/32,
center - size/32,
center,
center + size/32,
],
'y1': [
center,
center - size/32,
center - size/32,
center - size/32,
center,
center - size/32,
center - size/32,
center - size/32
],
'x2': [
center,
diagonalMax,
horiVertMax,
diagonalMax,
center,
diagonalMin,
horiVertMin,
diagonalMin
],
'y2': [
horiVertMin,
diagonalMin,
center,
diagonalMax,
horiVertMax,
diagonalMax,
center,
diagonalMin
],
'x3': [
center + size/32,
center + size/32,
center,
center - size/32,
center + size/32,
center + size/32,
center,
center - size/32
],
'y3': [
center,
center + size/32,
center + size/32,
center + size/32,
center,
center + size/32,
center + size/32,
center + size/32
]
}
//draw the circles that represent the saturation dimension
p5.stroke(0);
for(var i = 0; i < 3; i++){
p5.fill(0, 0,satCircles['brightness'][i], satCircles['alpha'][i]);
p5.ellipse(center, center, satCircles['size'][i]);
}
//draw the hexagon that represents the brightness dimension
var oct_sat = 0;
if(spot_hue < 360){
oct_sat = 100;
}
p5.fill(spot_hue, oct_sat, 100, brightnessTrans);
octagon(p5, center, center, size/3);
//draw the stars that represent the hue dimension
p5.noStroke();
p5.angleMode(p5.DEGREES);
p5.translate(center, center);
p5.rotate(hueDegree);
var hsba = Array(spot_hue, 0, 100, hueTrans);
if(spot_hue < 360){
hsba[1] = 100;
}
drawStar(p5, hsba, positions, 3);
p5.rotate(-hueDegree);
p5.translate(-center, -center);
hsba = Array(spot_hue, 100, 100, hueTrans);
drawStar(p5, Array(0, 0, 100, 0.8), positions);
//reset various settings to their previous values before this functoin was called
p5.noFill();
p5.angleMode(p5.RADIANS);
p5.translate(-(cx - center), -(cy - center));
}
}
/*
* function to draw a hexagon shape
* adapted from: https://p5js.org/examples/form-regular-polygon.html
* @param {Object} p5 - the p5.js object
* @param {Number} x - x-coordinate of the hexagon
* @param {Number} y - y-coordinate of the hexagon
* @param {Number} radius - radius of the hexagon
*/
function octagon(p5, x, y, radius) {
p5.angleMode(p5.RADIANS);
var angle = p5.TWO_PI / 8;
p5.beginShape();
for (var a = p5.TWO_PI/16; a < p5.TWO_PI + p5.TWO_PI/16; a += angle) {
var sx = x + p5.cos(a) * radius;
var sy = y + p5.sin(a) * radius;
p5.vertex(sx, sy);
}
p5.endShape(p5.CLOSE);
}
/*
* function to draw the 8 triangles that repressnt the hue dimension
* the colour and transparency level of the triangles is also affected by the brightness dimension
* @param {Object} p5 - the p5.js object
* @param {Array} hsba - Array of values used to set the values for the fill function
* @param {Object} positions - Object containing all the x and y positions of the 8 triangles that make up the star
* @param {Number} sizeReducer - Variable that allows the star to drawn at smaller size, should be greater than 1
*/
function drawStar(p5, hsba, positions, sizeReducer = 1) {
p5.fill(hsba[0], hsba[1], hsba[2], hsba[3]);
for($i = 0; $i < 8; $i++){
p5.triangle(positions['x1'][$i]/sizeReducer, positions['y1'][$i]/sizeReducer, positions['x2'][$i]/sizeReducer, positions['y2'][$i]/sizeReducer, positions['x3'][$i]/sizeReducer, positions['y3'][$i]/sizeReducer);
}
}
<!DOCTYPE html>
<html>
<head>
<title>Rainbow Spiral Map</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.2.0/dist/leaflet.css" />
<style>
body {
padding: 0;
margin: 0;
}
html, body, #map {
height: 100%;
width: 100%;
overflow: hidden;
}
.leaflet-control-attribution {
font-size: 24px !important;
}
</style>
</head>
<body>
<div id="map"></div>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.14/p5.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.14/addons/p5.dom.js"></script>
<script src="https://unpkg.com/leaflet@1.2.0/dist/leaflet.js"></script>
<script src="leaflet-hash.js"></script>
<script src="grayglyph.js"></script>
<script src="sketch.js"></script>
<script src="map.js"></script>
</body>
</html>
<head>
<style> body {padding: 0; margin: 0;} </style>
</head>
<body style="background-color:white">
<img src="sketch.png" width="960" height="720"/>
</body>
(function(window) {
var HAS_HASHCHANGE = (function() {
var doc_mode = window.documentMode;
return ('onhashchange' in window) &&
(doc_mode === undefined || doc_mode > 7);
})();
L.Hash = function(map) {
this.onHashChange = L.Util.bind(this.onHashChange, this);
if (map) {
this.init(map);
}
};
L.Hash.parseHash = function(hash) {
if(hash.indexOf('#') === 0) {
hash = hash.substr(1);
}
var args = hash.split("/");
if (args.length == 5) {
var seed = parseInt(args[0], 10),
zoom = parseInt(args[1], 10),
lat = parseFloat(args[2]),
lon = parseFloat(args[3]);
depth = parseFloat(args[4]);
if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) {
return false;
} else {
return {
center: new L.LatLng(lat, lon),
zoom: zoom,
seed: seed,
depth: depth
};
}
} else {
return false;
}
};
L.Hash.formatHash = function(map) {
var seed = map._p5_seed,
depth = map._p5_depth,
center = map.getCenter(),
zoom = map.getZoom(),
precision = 12;
// precision = Math.max(0, Math.ceil(Math.log(zoom*zoom) / Math.LN2));
return "#" + [seed, zoom,
center.lat.toFixed(precision),
center.lng.toFixed(precision),
depth.toFixed(precision)
].join("/");
},
L.Hash.prototype = {
map: null,
lastHash: null,
parseHash: L.Hash.parseHash,
formatHash: L.Hash.formatHash,
init: function(map) {
this.map = map;
// reset the hash
this.lastHash = null;
this.onHashChange();
if (!this.isListening) {
this.startListening();
}
},
removeFrom: function(map) {
if (this.changeTimeout) {
clearTimeout(this.changeTimeout);
}
if (this.isListening) {
this.stopListening();
}
this.map = null;
},
onMapMove: function() {
// bail if we're moving the map (updating from a hash),
// or if the map is not yet loaded
if (this.movingMap || !this.map._loaded) {
return false;
}
var hash = this.formatHash(this.map);
if (this.lastHash != hash) {
location.replace(hash);
this.lastHash = hash;
}
},
movingMap: false,
update: function() {
var hash = location.hash;
if (hash === this.lastHash) {
return;
}
var parsed = this.parseHash(hash);
if (parsed) {
var do_reset = false;
if (!("_hash_parsed" in this.map)) {
do_reset = true;
}
this.map._hash_parsed = true;
this.map._p5_seed = parsed.seed;
this.map._p5_depth = parsed.depth;
this.movingMap = true;
this.map.setView(parsed.center, parsed.zoom, {reset: do_reset});
this.movingMap = false;
}
else if (!("_hash_parsed" in this.map)) {
this.map._hash_parsed = true;
var center = this.map.getCenter();
var zoom = this.map.getZoom();
this.map.setView(center, zoom, {reset: true});
}
else {
this.onMapMove(this.map);
}
},
// defer hash change updates every 100ms
changeDefer: 100,
changeTimeout: null,
onHashChange: function() {
// throttle calls to update() so that they only happen every
// `changeDefer` ms
if (!this.changeTimeout) {
var that = this;
this.changeTimeout = setTimeout(function() {
that.update();
that.changeTimeout = null;
}, this.changeDefer);
}
},
isListening: false,
hashChangeInterval: null,
startListening: function() {
this.map.on("moveend", this.onMapMove, this);
if (HAS_HASHCHANGE) {
L.DomEvent.addListener(window, "hashchange", this.onHashChange);
} else {
clearInterval(this.hashChangeInterval);
this.hashChangeInterval = setInterval(this.onHashChange, 50);
}
this.isListening = true;
},
stopListening: function() {
this.map.off("moveend", this.onMapMove, this);
if (HAS_HASHCHANGE) {
L.DomEvent.removeListener(window, "hashchange", this.onHashChange);
} else {
clearInterval(this.hashChangeInterval);
}
this.isListening = false;
}
};
L.hash = function(map) {
return new L.Hash(map);
};
L.Map.prototype.addHash = function() {
this._hash = L.hash(this);
};
L.Map.prototype.removeHash = function() {
this._hash.removeFrom();
};
})(window);
var myCRS = L.extend({}, L.CRS.Simple, {
transformation: new L.Transformation(1, 0,
// -1, // works like expected
1, // image travels while zooming
0)
});
if (typeof initialZoomLevel === 'undefined') {
var initialZoomLevel = 0;
}
if (typeof maxZoomLevel === 'undefined') {
var maxZoomLevel = 16;
}
var worldMap = new L.Map('map', {
continuousWorld:true,
minZoom: 0,
maxZoom: maxZoomLevel,
crs: myCRS,
attributionControl: false,
center: [512, 512],
zoom: initialZoomLevel});
worldMap._p5_seed = Math.floor(Math.random() * 1000);
worldMap._p5_depth = 0.0;
// console.log("Seed start", worldMap._p5_seed)
// Assuming your map instance is in a variable called map
var hash = new L.Hash(worldMap);
// console.log("Seed now", worldMap._p5_seed)
var s = function( p ) {
p.setup = function() {
canvas = p.createCanvas(p._L_width, p._L_height);
p.noLoop();
};
p.draw = function() {
if ("_L_size" in p && "_L_nw" in p) {
var nw = p._L_nw;
var t_size = p._L_size;
var zoom = p._L_zoom;
var m_x1 = nw.lng;
var m_y1 = nw.lat;
var m_x2 = m_x1 + t_size;
var m_y2 = m_y1 + t_size;
var depth = p._L_depth;
p.noiseSeed(p._L_seed)
drawGrid(p, m_x1, m_x2, m_y1, m_y2, depth, zoom);
}
};
};
var tiles = new L.GridLayer({continuousWorld: true});
tiles.createTile = function(coords) {
if (!("_hash_parsed" in worldMap)) {
return L.DomUtil.create('canvas', 'leaflet-tile');
}
var size = this.getTileSize();
var myp5 = new p5(s);
myp5._L_width = size.x;
myp5._L_height = size.y;
myp5._L_zoom = coords.z;
myp5._L_seed = worldMap._p5_seed;
myp5._L_depth = worldMap._p5_depth;
myp5._L_coords = coords;
// calculate projection coordinates of top left tile pixel
myp5._L_nwPoint = coords.scaleBy(size);
myp5._L_size = 256.0 / Math.pow(2, coords.z)
// calculate geographic coordinates of top left tile pixel
myp5._L_nw = worldMap.unproject(myp5._L_nwPoint, coords.z)
myp5._start();
var tile = myp5.canvas;
L.DomUtil.addClass(tile, 'leaflet-tile');
return tile;
}
tiles.addTo(worldMap)
var curLinkIndex = 0;
linkHome = "#0/0/512/512/0"
if (typeof tourPath === 'undefined') {
var tourPath = [
[2, 512, 512],
[4, 512, 512],
[6, 512, 512],
[8, 512, 512]
]
}
tourPath.unshift([initialZoomLevel, 512, 512]);
if (typeof tourSeed === 'undefined') {
var tourSeed = 0;
}
function clickHome() {
worldMap.flyTo([tourPath[0][1], tourPath[0][2]], tourPath[0][0]);
}
function clickDemo() {
if(worldMap._p5_seed != tourSeed) {
var center = worldMap.getCenter();
var zoom = worldMap.getZoom();
worldMap._p5_seed = tourSeed;
tiles.redraw();
// worldMap.setView(center, zoom, {reset: true});
curLinkIndex = 0;
}
else {
curLinkIndex = (curLinkIndex + 1) % tourPath.length
}
var curDest = tourPath[curLinkIndex]
worldMap.flyTo([curDest[1], curDest[2]], curDest[0]);
}
function clickReset() {
window.location.reload();
}
attrib = new L.Control.Attribution
attrib.setPrefix("")
attrStr = '<a href="#" onclick="javascript:clickHome();">home</a> | '
attrStr += '<a href="#" onclick="javascript:clickReset();">reset</a> | '
attrStr += '<a href="#" onclick="javascript:clickDemo();">tour</a>'
attrib.addAttribution(attrStr)
worldMap.addControl(attrib)
{
"commits": [
{
"sha": "28724ed04b5dbf51097c896316a88065d3db166f",
"name": "final_version"
},
{
"sha": "6cd2ed74cb7da54cbaa2ee8a6b4aab03f7e1ac3b",
"name": "layering_details"
},
{
"sha": "3fe2a3f36a3b2cef15b952a5a3093acd079f375e",
"name": "infinite_zoomable_sketch"
},
{
"sha": "30a66bd84e44c83d6ba4bd6243cdd9c1083866df",
"name": "failed_experiment"
},
{
"sha": "35710f99add255cdf4748a571b50f07828219239",
"name": "sketch"
}
]
}
//for debugging
var debug = false;
//this object is used to store all possible locations for octagon zones
var octagonZone = {
//width and height of a octagon zone
zoneSize: 60,
//multiplier - used for many calculations related to this object
multiplier: 0,
//co-ordinates for each glyph within the octagon zone
//drawn from the center to the outside - the last twelve co-ordinates can be used to draw the octagon outline
innerCoordinates: [],
/*
* initilizes the octagonZone
* sets the value of the mulitplier and load values into the innerCoordinates and locations arrays
*/
init: function() {
this.multiplier = this.zoneSize /12;
octagonZone.createInnerCoordinates();
octagonZone.createLocations();
},
/*
* this function sets the values of the innerCoordinates array
* once the value for the multiplie is known
*/
createInnerCoordinates: function(){
this.innerCoordinates = [
[+this.multiplier*1, -this.multiplier*1], [+this.multiplier*1, +this.multiplier*1], [-this.multiplier*1, +this.multiplier*1], [-this.multiplier*1, -this.multiplier*1],
[+this.multiplier*1, -this.multiplier*3], [+this.multiplier*3, -this.multiplier*1], [+this.multiplier*3, +this.multiplier*1], [+this.multiplier*1, +this.multiplier*3],
[-this.multiplier*1, +this.multiplier*3], [-this.multiplier*3, +this.multiplier*1], [-this.multiplier*3, -this.multiplier*1], [-this.multiplier*1, -this.multiplier*3],
[+this.multiplier*1, -this.multiplier*5], [+this.multiplier*3, -this.multiplier*3], [+this.multiplier*5, -this.multiplier*1], [+this.multiplier*5, +this.multiplier*1],
[+this.multiplier*3, +this.multiplier*3], [+this.multiplier*1, +this.multiplier*5], [-this.multiplier*1, +this.multiplier*5], [-this.multiplier*3, +this.multiplier*3],
[-this.multiplier*5, +this.multiplier*1], [-this.multiplier*5, -this.multiplier*1], [-this.multiplier*3, -this.multiplier*3], [-this.multiplier*1, -this.multiplier*5]
];
},
//array of all possible zone locations
locations: [],
/*
* this function sets the values of the locations array
* determining every possible location for a octagon zone
*/
createLocations: function(){
//keeping track of performance
var start = performance.now();
//calculate the step sizes
var multiplier = this.zoneSize/12, xStep = this.multiplier*8, yStep = this.multiplier*6;
//set up initial values required for while loop
var endPointer = 4, centerX = 512, centerY = 512 + yStep, direction = 'left';
while(endPointer < 5000){
for(var zone=1; zone <= endPointer; zone++){
if(direction == 'left'){
centerX -= xStep;
centerY -= yStep;
}
else if(direction == 'up'){
centerX += xStep;
centerY -= yStep;
}
else if(direction == 'right'){
centerX += xStep;
centerY += yStep;
}
else if(direction == 'down'){
centerX -= xStep;
centerY += yStep;
}
//load the zone into the locations array
this.locations.push([centerX, centerY]);
if( (zone % (endPointer/4) ) == 0){
direction = octagonZone.changeDirection(direction);
}
}
centerY += (yStep *2);
endPointer += 8;
}
var end = performance.now();
if(debug){
console.log('zone locations loaded in ', (end - start).toFixed(4), ' milliseconds');
}
},
/*
* direction is used to determine how the centerX and centerY values in createLocations function will be mutated
* @param {String} currentDirection - eg left, right
* @return {String} nextDirection - eg left, right
*/
changeDirection: function(currentDirection){
var nextDirection = '';
switch (currentDirection) {
case "left":
nextDirection = "up";
break;
case "up":
nextDirection = "right";
break;
case "right":
nextDirection = "down";
break;
case "down":
nextDirection = "left";
break;
}
return nextDirection;
},
/*
* this function determines what stroke weight should be used
* then calls the other functions required to draw everything container within this zone
* @param {Object} p5 - the p5.js object
* @param {Number} centerX - center of the x-axis for the current octagon zone
* @param {Number} centerY - center of the y-axis for the current octagon zone
* @param {Number} x1 - left side of a map tile
* @param {Number} x2 - right side of a map tile
* @param {Number} y1 - top side of a map tile
* @param {Number} y2 - bottom side of a map tile
* @param {Number} hue - used to create p5.js color objects
* @param {Number} zm - current zoom level on the map
*/
drawZone: function(p5, centerX, centerY, x1, x2, y1, y2, hue, zm) {
var zero = p5.map(0, x1, x2, 0, 256);
var maxWidth = p5.map(this.multiplier*0.2, x1, x2, 0, 256);
if(zm < 5){
maxWidth = p5.map(this.multiplier*0.2 + (zm/5), x1, x2, 0, 256);
}
var weight = maxWidth - zero;
var colour = p5.color(hue, 100, 100);
if(zm >= 2){
weight = weight/2;
}
var loopLimits = 2;
if(zm >= 4){
loopLimits = 1;
}
if(zm >= 7){
loopLimits = 0;
}
for (var adjuster = -loopLimits; adjuster <= loopLimits; adjuster++) {
this.drawOutline(p5, centerX, centerY, x1, x2, y1, y2, weight, colour, zm, adjuster);
}
//at zoom level 5 or greater
//a special glpyh is introduced at the center of the octagon zone
if(zm >= 5){
drawGrayGlyph(p5, [hue, 0, 50], centerX, centerY, x1, x2, y1, y2, zm, hue);
}
this.drawGlyphs(p5, centerX, centerY, x1, x2, y1, y2, weight, hue, zm);
},
/*
* draws the outline that connects the outer points of a octagon zone together
* @param {Object} p5 - the p5.js object
* @param {Number} centerX - center of the x-axis for the current octagon zone
* @param {Number} centerY - center of the y-axis for the current octagon zone
* @param {Number} x1 - left side of a map tile
* @param {Number} x2 - right side of a map tile
* @param {Number} y1 - top side of a map tile
* @param {Number} y2 - bottom side of a map tile
* @param {Number} weight - stroke weight of the shape to be drawn
* @param {Object} colour - a p5.js color object - determines the stroke colour
* @param {Number} zm - current zoom level on the map
* @param {Number} adjuster - can be used to adjust the size of the outline
*/
drawOutline: function(p5, centerX, centerY, x1, x2, y1, y2, weight, colour, zm, adjuster = 0) {
var xPos, yPos, cx, cy;
//adjust the opacity of the colour
colour._array[3] = 1 - (0.2 * Math.abs(adjuster));
p5.stroke(colour);
p5.strokeWeight(weight);
p5.beginShape();
for (var pos = 12; pos < octagonZone.innerCoordinates.length; pos++) {
var xPos = octagonZone.innerCoordinates[pos][0];
var yPos = octagonZone.innerCoordinates[pos][1];
if(xPos > 0){
if(xPos == this.multiplier || xPos == (this.multiplier * 3)){
xPos += adjuster/2;
}
else {
xPos += adjuster;
}
}
else {
if(xPos == -this.multiplier || xPos == (-this.multiplier * 3)){
xPos -= adjuster/2;
}
else {
xPos -= adjuster;
}
}
if(yPos > 0){
if(yPos == this.multiplier){
yPos += adjuster/2;
}
else {
yPos += adjuster;
}
}
else {
if(yPos == -this.multiplier){
yPos -= adjuster/2;
}
else {
yPos -= adjuster;
}
}
cx = p5.map(centerX + xPos, x1, x2, 0, 256);
cy = p5.map(centerY + yPos, y1, y2, 0, 256);
p5.vertex(cx, cy);
}
p5.endShape(p5.CLOSE);
},
//a small collection of numbers represent different shapes
//used to determine what shapes within each glyph drawn within the drawGlyph function
shapeSelector: [1,3,4,5,6,7,8,9,10,11],
/*
* draws each of the glyphs contained within a octagon zone
* @param {Object} p5 - the p5.js object
* @param {Number} centerX - center of the x-axis for the current octagon zone
* @param {Number} centerY - center of the y-axis for the current octagon zone
* @param {Number} x1 - left side of a map tile
* @param {Number} x2 - right side of a map tile
* @param {Number} y1 - top side of a map tile
* @param {Number} y2 - bottom side of a map tile
* @param {Number} weight - stroke weight of the shape to be drawn
* @param {Number} hue - used to create p5.js color objects
* @param {Number} zm - current zoom level on the map
*/
drawGlyphs: function(p5, centerX, centerY, x1, x2, y1, y2, weight, hue, zm){
var glyphWidth = 256 / ((x2-x1)/ (this.multiplier * 1.6)) / 2;
var innerShapeSize = glyphWidth / 2;
//work out which colours will be used
var toHue = hue >= 180 ? hue - 180 : hue + 180;
var fromColour = p5.color(hue, 100, 100, 1 - (zm/20));
var toColour = p5.color(toHue, 100, 100, 1 - (zm/20));
var colour = "", lerpAmount = zm >= 3 ? 1 : 0;
var isVisible = false;
for (var pos = 0; pos < octagonZone.innerCoordinates.length; pos++) {
var xPos = centerX + octagonZone.innerCoordinates[pos][0];
var yPos = centerY + octagonZone.innerCoordinates[pos][1];
cx = p5.map(xPos, x1, x2, 0, 256);
cy = p5.map(yPos, y1, y2, 0, 256);
colour = p5.lerpColor(fromColour, toColour, lerpAmount);
p5.stroke(colour);
p5.strokeWeight(weight);
if(zm < 1){
p5.ellipse(cx, cy, glyphWidth*2);
}
var shapeSides = this.getNumberOfSides(p5, xPos, yPos);
if(zm < 4){
if(zm > 0){
p5.strokeWeight(weight/2);
}
polygon(p5, cx, cy, innerShapeSize, shapeSides);
if(zm > 0){
polygon(p5, cx, cy, glyphWidth, shapeSides);
}
}
else {
isVisible = isShapeWithinTile(xPos, yPos, x1, x2, y1, y2, octagonZone.multiplier);
if(isVisible){
var shapeSize = innerShapeSize;
if(zm < 7){
this.drawGlyphPattern(p5, cx, cy, shapeSize, shapeSides, zm);
}
else {
shapeSize = shapeSize * 1.5;
var splitShapes = this.splitShape(cx, cy, shapeSize, zm);
var sizeDivider = (zm - 6) * 2;
for(var i = 0; i < splitShapes.length; i++){
this.drawGlyphPattern(p5, splitShapes[i][0], splitShapes[i][1], innerShapeSize/4, shapeSides, zm);
}
p5.strokeWeight(16);
polygon(p5, cx, cy, glyphWidth/4, shapeSides);
}
}
}
if(zm >= 3){
//change the lerpAmount at certain points of the iteration
if(pos == 3){
lerpAmount = 0.5;
}
if(pos >= 11){
lerpAmount = 0;
}
}
}
},
/*
* takes the center co-ordinates of a shape and splits it into a new set of shapeSides
* at zoom level 7 a shape is split into 8
* at zoom level 8 and above it is split into 16 shapes
* @param {Number} cx - center x co-ordinates for a shape
* @param {Number} cy - center y co-ordinates for a shape
* @param {Number} shapeSize - the size of the shape being split
* @param {Number} zm - the current zoom level
*/
splitShape: function(cx, cy, shapeSize, zm){
var newShapes = [
[cx - shapeSize, cy - shapeSize],
[cx + shapeSize, cy - shapeSize],
[cx - shapeSize, cy + shapeSize],
[cx + shapeSize, cy + shapeSize],
[cx, cy - shapeSize],
[cx, cy + shapeSize],
[cx - shapeSize, cy],
[cx + shapeSize, cy]
];
if(zm >= 8){
newShapes = [
[cx - shapeSize*0.5, cy - shapeSize*0.5],
[cx - shapeSize*0.5, cy + shapeSize*0.5],
[cx - shapeSize*0.5, cy - shapeSize*1.25],
[cx - shapeSize*0.5, cy + shapeSize*1.25],
[cx - shapeSize*1.25, cy - shapeSize*0.5],
[cx - shapeSize*1.25, cy + shapeSize*0.5],
[cx - shapeSize*1.25, cy - shapeSize*1.25],
[cx - shapeSize*1.25, cy + shapeSize*1.25],
[cx + shapeSize*0.5, cy - shapeSize*0.5],
[cx + shapeSize*0.5, cy + shapeSize*0.5],
[cx + shapeSize*0.5, cy - shapeSize*1.25],
[cx + shapeSize*0.5, cy + shapeSize*1.25],
[cx + shapeSize*1.25, cy - shapeSize*0.5],
[cx + shapeSize*1.25, cy + shapeSize*0.5],
[cx + shapeSize*1.25, cy - shapeSize*1.25],
[cx + shapeSize*1.25, cy + shapeSize*1.25]
];
}
return newShapes;
},
/*
* Determines the number of sides a shape should have based on the center x and center y co-ordinates for the shape
* @param {Object} p5 - the p5.js object
* @param {Number} x - center x co-ordinates for a shape
* @param {Number} y - center y co-ordinates for a shape
* @return{Number} shapeSides - how many sides the shape will have
*/
getNumberOfSides: function(p5, x, y){
var noiseValue = p5.noise(x, y);
//reverse and stretch the noiseValue out a bit so there is a higher change a circle will be drawn
noiseValue = p5.map(noiseValue, 0.2, 0.8, 0.0, 1.0);
var shapeSelectorSum = 0, shapeFound = false, shapeSides = 0;
for(var i in this.shapeSelector){
shapeSelectorSum += this.shapeSelector[i];
if(noiseValue <= (shapeSelectorSum/100)){
shapeSides = this.shapeSelector[this.shapeSelector.length - i - 1];
shapeFound = true;
break;
}
}
if(!shapeFound){
shapeSides = 12;
}
return shapeSides;
},
//stroke weights used for the glyph pattern at various zoom levels
glyphPatternStrokeWeights: [8, 4, 2, 2],
/*
* draws a pattern by rotating a shape several times
* @param {Object} p5 - the p5.js object
* @param {Number} x - center of the x-axis for the current glyph
* @param {Number} y - center of the y-axis for the current glyph
* @param {Number} shapeSize - size of the shape
* @param {Number} numOfSides - how many sides the shape has
* @param {Number} zm - current zoom level on the map
*/
drawGlyphPattern: function(p5, x, y, shapeSize, numOfSides, zm){
var rotationOptions = [2,3,4,6,8,12,15,18];
var numOfRotations = zm <= 10 ? rotationOptions[zm-4] : rotationOptions[6];
var shapeLoopLimit = zm - 4;
var weight = this.glyphPatternStrokeWeights[shapeLoopLimit];
p5.translate(x, y);
if(shapeLoopLimit >= 4){
weight = 1;
}
p5.strokeWeight(weight);
for (var i = 0; i < (numOfRotations * 2); i ++) {
for (var j = 0; j <=shapeLoopLimit; j++) {
polygon(p5, 0, shapeSize + (j*3), shapeSize + (j*3), numOfSides);
}
p5.rotate(p5.PI/numOfRotations);
}
p5.translate(-x, -y);
}
}
octagonZone.init();
/* the random number seed for the tour */
var tourSeed = 100;
/* triplets of locations: zoom, x, y */
var tourPath = [
[1, 232, 487],
[3, 232, 487],
[5, 232, 487],
[7, 137, 187],
[7, 120, 192],
[0, -36750, 536],
[0, -1654, -2081],
[0, 512, 50232],
[1, 512, 50232],
[2, 512, 50232],
[3, 512, 50232],
[4, 512, 50232],
[5, 512, 50232],
[6, 512, 50232],
[7, 512, 50232],
[8, 512, 50232],
[9, 512, 50232],
[10, 512, 50232],
[0, 512, 512],
[1, 512, 512],
[2, 512, 512],
[3, 512, 512],
[4, 512, 512],
[5, 512, 512],
[6, 512, 512],
[7, 512, 512],
[8, 512, 512],
[9, 512, 512],
[10, 512, 512]
];
/* what is the initial zoom level (defaults to 0) */
var initialZoomLevel = 0;
/* what is the maximum zoom level (make this at least 10. defaults to 16) */
var maxZoomLevel = 10;
/*
* draws the contents a map tile
* @param {Object} p5 - the p5.js object
* @param {Number} x1 - left side of a map tile
* @param {Number} x2 - right side of a map tile
* @param {Number} y1 - top side of a map tile
* @param {Number} y2 - bottom side of a map tile
* @param {Number} z - use this as the noise z offset (can be shifted)
* @param {Number} zoom - current zoom level (starts at 0), useful to decide how much detail to draw
*/
function drawGrid(p5, x1, x2, y1, y2, z, zoom) {
//unique key for the tile - useful for debugging
var tileKey = x1 + '-' + x2 + '-' + y1 + '-' + y2;
p5.background(0);
p5.colorMode(p5.HSB);
p5.rectMode(p5.CORNERS);
p5.noFill();
if(debug){
console.log(tileKey);
drawDebugFrame(p5, x1, x2, y1, y2);
}
p5.rectMode(p5.CENTER);
var hue = 0;
var colour = p5.color(0, 100, 100);
var zoneX = 0, zoneY = 0, isVisible = false;
for (var i = 0, len = octagonZone.locations.length; i < len; i++) {
zoneX = octagonZone.locations[i][0];
zoneY = octagonZone.locations[i][1];
isVisible = isShapeWithinTile(zoneX, zoneY, x1, x2, y1, y2, octagonZone.zoneSize/2);
if(isVisible){
hue = (i % 24) * 15;
setTimeout(
function(p5js, cX, cY, leftX, rightX, topY, bottomY, h, zm){
return function() { octagonZone.drawZone(p5js, cX, cY, leftX, rightX, topY, bottomY, h, zm); };
}(p5, zoneX, zoneY, x1, x2, y1, y2, hue, zoom),
0
);
if(i < 4 && zoom >= 5){
drawGrayGlyph(p5, [350, 100, 75], 512, 512, x1, x2, y1, y2, zoom);
}
}
}
}
/*
* determines if a shape exists within the current tile
* if it can't be seen within the current tile then there is no need to draw it
* @param {Number} centerX - center of the x-axis for the current octagon zone
* @param {Number} centerY - center of the y-axis for the current octagon zone
* @param {Number} x1 - left side of a map tile
* @param {Number} x2 - right side of a map tile
* @param {Number} y1 - top side of a map tile
* @param {Number} y2 - bottom side of a map tile
* @param {Number} shapeEdge - distance from the center to the edge of the shape
* @return{Boolean} - whether or not the zone is within the tile
*/
function isShapeWithinTile(centerX, centerY, x1, x2, y1, y2, shapeEdge){
if((centerX + shapeEdge) <= x1){
return false;
}
if((centerX - shapeEdge) >= x2){
return false;
}
if((centerY + shapeEdge) <= y1){
return false;
}
if((centerY - shapeEdge) >= y2){
return false;
}
return true;
}
/*
* function to draw a polygon shape
* adapted from: https://p5js.org/examples/form-regular-polygon.html
* @param {Object} p5 - the p5.js object
* @param {Number} x - x-coordinate of the polygon
* @param {Number} y - y-coordinate of the polygon
* @param {Number} radius - radius of the polygon
* @param {Number} npoints - number of sides the polygon has
*/
function polygon(p5, x, y, radius, npoints) {
if(npoints > 1){
var angle = p5.TWO_PI / npoints;
p5.angleMode(p5.RADIANS);
p5.beginShape();
for (var a = p5.TWO_PI/(npoints*2); a < p5.TWO_PI + p5.TWO_PI/(npoints*2); a += angle) {
var sx = x + p5.cos(a) * radius;
var sy = y + p5.sin(a) * radius;
p5.vertex(sx, sy);
}
p5.endShape(p5.CLOSE);
}
else {
p5.ellipse(x, y, radius * 2);
}
}
/*
* creates a news GrayGlyph object and then draws it with a size depending on the current zoom level
* @param {Object} p5 - the p5.js object
* @param {Array} hsb - array of values representing a colour in HSB mode
* @param {Number} centerX - center of the x-axis for the current octagon zone
* @param {Number} centerY - center of the y-axis for the current octagon zone
* @param {Number} x1 - left side of a map tile
* @param {Number} x2 - right side of a map tile
* @param {Number} y1 - top side of a map tile
* @param {Number} y2 - bottom side of a map tile
* @param {Number} zm - current zoom level on the map
* @param {Number} spot_hue - value of the hue - a number between 0 and 359
*/
function drawGrayGlyph(p5, hsb, centerX, centerY, x1, x2, y1, y2, zm, spot_hue = 360) {
var colorGlpyhSizes = [12.5, 50, 200, 400, 800, 1600];
var sizePointer = zm < 5 ? 0 : zm - 5;
var glyph = new GrayGlyph();
glyph.draw(hsb, colorGlpyhSizes[sizePointer], p5, centerX, centerY, x1, x2, y1, y2, spot_hue);
}
/*
* function to draw a red rectangle to show how big the frame used on blocks.org is
* @param {Object} p5 - the p5.js object
* @param {Number} x1 - left side of a map tile
* @param {Number} x2 - right side of a map tile
* @param {Number} y1 - top side of a map tile
* @param {Number} y2 - bottom side of a map tile
*/
function drawDebugFrame(p5, x1, x2, y1, y2){
var cx = p5.map(512-960/2, x1, x2, 0, 256);
var cy = p5.map(512-720/2, y1, y2, 0, 256);
var cx2 = p5.map(512+960/2, x1, x2, 0, 256);
var cy2 = p5.map(512+720/2, y1, y2, 0, 256);
p5.stroke(0, 100, 100);
p5.rect(cx, cy, cx2, cy2);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment