Cover photo

Triage: Generative Series Breakdown

https://highlight.xyz/mint/64eec29adb2d22cad96e3151

     The basis of Triage is Delaunay triangulation (DT) from the d3-delaunay library which is based on the Delaunator by Volodymyr Agafonkin. It is a “technique for creating a mesh of contiguous, nonoverlapping triangles from a dataset of points.”

Liu Yonghe, Feng Jinming, Shao Yuehong
Liu Yonghe, Feng Jinming, Shao Yuehong

The algorithm is based on ideas from the following papers:

Early test using points from a simplex noise field
Early test using points from a simplex noise field
Another example with circle packing
Another example with circle packing

     The first step of drawing an iteration of Triage is the creation of a small random vector path in the center of the canvas containing about twenty segments. Then, the center of the shape is moved to the center of the canvas using geometric.js.

     In order to fill the canvas efficiently, an optimal rotation for the shape must be found. A bounding box will be used to compare the shape W x L ratio to the canvas ratio. By rotating the shape and its bounding box 360 degrees in 30 degree increments and recording the ratio for each turn, a sample of 12 possible rotations with corresponding ratios can be used to find the optimal rotation.

Ready to be scaled up
Ready to be scaled up

     The array of points is given to the function geometric.polygonScaleArea() which scales up the shape from the center until the edge of the canvas is detected. The next step loops through the paths of the shape to create Bezier paths using the bezier.js function new Bezier(p1.x,p1.y,p2.x,p2.y,p3.x,p3.y,p4.x,p4.y). The second part of the loop “generates a curve’s outline at distance d along the curve normal and anti-normal. The result is an array of curves that taken together form the outline path for this curve.”

https://pomax.github.io/bezierjs/

    for(b=3;b<rotPoly_s.length;b+=3){
      //generate curve
      const bcurve = new Bezier(
        rotPoly_s[b][0], rotPoly_s[b][1],
        rotPoly_s[b-1][0], rotPoly_s[b-1][1],
        rotPoly_s[b-2][0], rotPoly_s[b-2][1],
        rotPoly_s[b-3][0], rotPoly_s[b-3][1]);

      //curve outlines
      or=hl.random(lo,hi)
      om=hl.random(1.2,1.3)
      for(var toffo=0; toffo<=1; toffo+=0.4) {
        var doc = c => drawCurve(c);
        var outline = bcurve.outline(or);
        outline.curves.forEach(doc);
        or=or*om
      }
    }
Offset outlines of the curves
Offset outlines of the curves

     Another great thing about bezier.js is the ability to “generate a LookUp Table of coordinates on the curve, spaced at parametrically equidistance intervals.” Inside the drawCurve function, the LUTs are found for each curve and stored to be used for the triangle mesh input.

function drawCurve(curve, offset){
...
  var LUT = zcurve.getLUT(LUTsteps);
  LUT.forEach(p => dlc.push([p.x,p.y]));
}
LUT points along the curves
LUT points along the curves

     The triangle mesh is generated by d3.js, then the resulting points are sorted into groups of three so that each triangle can be edited individually.

  let verts = newVerts
  voronoi = d3.voronoi();
  let triangles = voronoi(verts).triangles();
  triangles.map(t => t.map(v => tris.push([v[0], v[1]])))

  count=0
  t=[]
  for(t=0;t<tris.length;t++){
      if(count==3){
          t.push([
            tris[t-3][0], tris[t-3][1],
            tris[t-2][0], tris[t-2][1],
            tris[t-1][0], tris[t-1][1]
          ])
      count=0
    }
    count++
  }

     Some filters are used to cull triangles that have at least one angle of less than 4 degrees, triangles that get too close to the canvas edge, and triangles that are over a certain size. Each triangle is then scaled down slightly (to create small gaps) and filled with hatch lines. Noise is mapped to the stroke weight, number of hatch lines, and angle of hatch lines to create interesting patterns in the texture of the mesh.

     That’s about it, thank you for reading this far.

Triage #1
Triage #1
//the end