An fx(hash) Template

By Wanda Oliver (original)

It also needs to be said that I had tons of help from the veterans.  Help that I am sincerely thankful for.  Setting down what they have taught me is an expression of that gratitude, paying it forward in the same spirit of generosity in which it was given.

I came into p5.js development with a background as a database developer, but with absolutely no experience in front end or web development.  I needed to be told how to begin as if a five year old.  Thinking that I can’t be the only creator in that position, I will endeavor to make no assumptions, to be clear, and to be complete.  Let’s dive in.

First of all, a sketch.js file is necessary, but insufficient.  You will also need an index.html file and a style.css file.  The index.html file, in particular, needs to include some specific lines of code customized for the fx(hash) platform - the default version supplied by your p5.js editor will not be sufficient.  I was given these files, and honestly I just use them.  Trying to understand them at the same time as trying to learn p5.js seemed a bridge too far.  The code for these two files is included below just as it was given to me and I suggest you take it, at least at the beginning, as two black boxes.  Likewise, I was given a p5.min.js file that I include in my deployment set.  It can be downloaded here. This is the root directory of one of my projects showing the files zipped and uploaded to the platform.

What follows is the heavily annotated sketch.js file that I have developed as the starting point for my projects. You can copy the code, paste it into your p5.js editor, and run it.  It will produce a small circle in the middle of your canvas.  But the meat is not in the output.  It is in the annotations.  Read them.  Then give it a try.   Feel free to contact me if you run into any obstacles or have questions.

// NAME YOUR PROJECT
// < project name>
// < project author>
// < date >
// none of this is essential, but I think it a good habit of intrinsic documentation


// +++++++++++++++++++ RECOMMENDATION ++++++++++++++++
// PREPARE A RESIZEABLE GRID SYSTEM
// It is strongly recommended that you use a dimensionless approach from the start
// You will see how this is used later in the code
let g = 0;  // will hold size of grid
let c = 0;  // will hold grid cell
let q = 0;  // will hold grid micro-cell
let v = 0;  // will grid nano-cell
//++++++++++++++++++++++++++++++++++++++++++++++++++++


// +++++++++++++++++++ DO NOT CHANGE +++++++++++++++++
// PREPARE VARIABLE TO CONTROL PRNG FUNCTIONALITY
let seed = 0; // to hold random seed
//++++++++++++++++++++++++++++++++++++++++++++++++++++


// PRE-LOAD
function preload()  
{
  // +++++++++++++++++++ DO NOT CHANGE +++++++++++++++++
  // SET SEED TO CONTROL PRNG FUNCTION REQUIRED BY fx(hash)
  seed = int(fxrand() * 999999);
  // +++++++++++++++++++++++++++++++++++++++++++++++++++


  // +++++++++++++++++++ RECOMMENDATION ++++++++++++++++
  // Initialize color system
  colorMode(HSB, 360, 100, 100, 100);
  // you will have finer control over color in your work if you use the HSB model from the start
  // +++++++++++++++++++++++++++++++++++++++++++++++++++
}  


// SET-UP
function setup() 
{
  // Initialize Grid System & Setup Canvas
  // the createCanvas and background statements are a standard part
  // of any p5.js project - the grid system is a recommendation
  setupGrid(1);       // use explained in the definition of the function 
  createCanvas(g,g);  // see the function setUpGrid below to understand the importance
                      // of using a variable here rather than a fixed numeral
  background(10,0,100,100); // set color of background; I'm using white
}


// DRAW
function draw() 
{
  // ++++++++++++ DO NOT CHANGE ++++++++++++++++++++++++++
  // Initialize PRNG environment required by fx(hash)
  randomSeed(seed);
  noiseSeed(seed);
  // DO MOT CALL ANY RANDOM FUNCTIONS PRIOR TO THESE TWO LINES!
  // You will see code in which these lines are placed in the setup function.
  // They need to go here in the draw function so that the resize and save functionality
  // given below work properly.  This will also be true of any code you
  // write that redraws the canvas.  Having these two statements in the draw
  // function will allow redrawing to occur while maintaining the 
  // deterministic quality required by fx(hash).
  // +++++++++++++++++++++++++++++++++++++++++++++++++++++
  

  // all your lovely drawing code goes here


  // here is a little snippet that shows an example of using
  // the grid system.  The variables x, y, & r are defined relative to the grid.
  // This allows them to redraw properly as the grid is resized.  See 
  // the setUpGrid function below for definitions of the variables g and q.
  stroke(10, 100, 0, 100);
  let x = g/2;
  let y = g/2;
  let r = 10*q;
  ellipse(x, y, r);

 
  

  // +++++++++++++++++++ DO NOT CHANGE +++++++++++++++++
  // REQUIRED BY FXHASH
  // call fxpreview once confirmation that preview is ready
  i = 0;
  while (i != 1) 
    {
      if ((isFxpreview = true)) {fxpreview(); i = 1;}
    }
  // +++++++++++++++++++++++++++++++++++++++++++++++++++
  

  // without the following your draw function will continuously loop
  // chances are you don't want that - at least not in the very beginning
  // of your learning curve
  noLoop();
}



// +++++++++++++++++++++++++++++++++++++++++++++++++++++
// PROJECT FUNCTIONS
// +++++++++++++++++++++++++++++++++++++++++++++++++++++


// This is where any code that you want to craft as a repeatable, callable
// module will go.  The Standard Template Functions below provide examples
// of how to declare functions.  I recommend that you  use functions
// liberally to organize your code for readability and ease.


 
// +++++++++++++++++++++++++++++++++++++++++++++++++++++
// STANDARD TEMPLATE FUNCTIONS - INCLUDE IN ALL PROJECTS
// +++++++++++++++++++++++++++++++++++++++++++++++++++++
function setupGrid(m)
{
  // m, the value passed into the function, is a multiplier used to scale the 
  // canvas for saving hi res output - see the keyPressed function below.
  // The value of m to begin with is 1 - see the call of this function in the
  // setUp function above.

  // g is the variable that controls the size of the canvas.  In this
  // case I am using a square canvas, hence the mininum value of windowWidth and 
  // windowHeight is used.

  // using windowWidth and windowHeight to establish the size of your grid allows
  // your canvas to respond to the device being used to view your work.  You will
  // often see work created with a fixed pixel size (e.g 1200 x 1200).  As a 
  // collector, I frown on this practice.  
  
  // you can read more about buildding dimensionless projects at
  // https://www.fxhash.xyz/article/how-to-create-a-dimensionless-project-with-p5js
  g = min(windowWidth, windowHeight) * m; 
  c = g/10;   // grid cell
  q = g/100;  // grid micro-cell
  v = g/1000; // grid nano-cell
  // the point of these divisions of the grid is that if you want
  // 1) to offer a work that scales to the window size of the viewer without any
  //    loss of quality, and
  // 2) produces the same output when given the same seed - a requirement for fx(hash) -
  //    you must specify all x and y positions, diameters, radii, heights and widths, and any other size 
  //    information in terms relative to your grid.  Having larger and smaller increments 
  //    ready to work with is handy.  When you think of size, don't forget things like textSize and strokeWeight.
}


// the built-in p5.js random function, when faced with something like random(0, 10)
// will never return a 10.  I find this unintuitive, so when I want a random integer
// that spans the full range of input values, I use the following.  This code was given to me 
// and I pass it along to you as extremely useful.
function randomInt(min, max) 
{
  return floor(random() * (max - min + 1)) + min;
}


// one of the things I look for as a collector is the ability to save a high resolution
// copy of my collected NFT - a file that would support printing a piece of wall art, for
// example.  This function adds that capability to your work.  When someone presses and s, the 
// canvas is resized.  This will inherently redraw your work.  The resized work is then downloaded.
// If you choose not to do  this, you may  remove this function.
function keyPressed()
{  
  if (key == 's' || key == 'S')
   {
    setupGrid(5);
    resizeCanvas(g,g);
    saveCanvas('wandaOliver_' + seed, 'png');
   }
}


Here is the index.html file I use.

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="p5.min.js"></script>
    <link rel="stylesheet" type="text/css" href="style.css">
    <meta charset="utf-8" />

  </head>
  <script id="fxhash-snippet">
  //---- do not edit the following code (you can indent as you wish)
  let alphabet = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
  var fxhash = "oo" + Array(49).fill(0).map(_=>alphabet[(Math.random()*alphabet.length)|0]).join('')
  fxhash = new URLSearchParams(window.location.search).get('hash') || fxhash
  let b58dec = str=>[...str].reduce((p,c)=>p*alphabet.length+alphabet.indexOf(c)|0, 0)
  let fxhashTrunc = fxhash.slice(2)
  let regex = new RegExp(".{" + ((fxhashTrunc.length/4)|0) + "}", 'g')
  let hashes = fxhashTrunc.match(regex).map(h => b58dec(h))
  let sfc32 = (a, b, c, d) => {
    return () => {
      a |= 0; b |= 0; c |= 0; d |= 0
      var t = (a + b | 0) + d | 0
      d = d + 1 | 0
      a = b ^ b >>> 9
      b = c + (c << 3) | 0
      c = c << 21 | c >>> 11
      c = c + t | 0
      return (t >>> 0) / 4294967296
    }
  }
  var fxrand = sfc32(...hashes)
  // true if preview mode active, false otherwise
  // you can append preview=1 to the URL to simulate preview active
  var isFxpreview = new URLSearchParams(window.location.search).get('preview') === "1"
  // call this method to trigger the preview
  function fxpreview() {
    console.log("fxhash: TRIGGER PREVIEW")
  }
  //---- /do not edit the following code
</script>
  <body>
    <main>
    </main>
    <script src="sketch.js"></script>
  </body>
</html>


This is the style.css file.

/* style.css */

html,body {
    margin: 0;
    padding: 0;
  background: #222;
}

main {
    text-align: center;
    width: 100%;
}

canvas {
  display: block;
}


main {
  align-items: center;
  display: flex;
  height: 100vh; /* Fallback for browsers that do not support Custom Properties */
  justify-content: center;
}
main canvas { 
  height: auto !important;
  width: auto !important;
  max-width: 100%;
  max-height: 100%;
  display: inline-block; /* Instead of display: block; */
  margin: 0 auto;
  vertical-align: middle;
  touch-action: none;
}

I hope it goes without saying that I see this template as a living document. If you have suggestions for improvement, I'd be only too happy to hear them.

Love and creativity always,

Wanda