block by enjalot 5a821ad0a05baa97105ee21d4ba5238b

thing 0004

Full Screen

thing 0004

pan & zoom by clicking and scrolling. playing with regl for webgl rendering

line rendering from this regl example: source code

adapted from pbeshai’s block and using npmcdn as found in vlandham’s block

great tutorial from pbeshai as well as one from vlandham

Built with blockbuilder.org

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://npmcdn.com/regl@1.3.0/dist/regl.js"></script>
    <script src="bundle.js"></script>
  <style>
    body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
  </style>
</head>

<body>
<script>
  const { push, unshift } = Array.prototype

  // https://github.com/regl-project/regl/blob/gh-pages/example/line.js

const links = {
  lineMesh (buffer, howMany, index) {
    for (let i = 0; i < howMany - 1; i++) {
      const a = index + i * 2
      const b = a + 1
      const c = a + 2
      const d = a + 3
      buffer.push(
        a, b, c,
        c, b, d)
    }
    return buffer
  }
}

const buffer = {
  duplicate (buffer, stride, dupScale) {
    if (stride == null) stride = 1
    if (dupScale == null) dupScale = 1
    const out = []
    const component = new Array(stride * 2)
    for (let i = 0, il = buffer.length / stride; i < il; i++) {
      const index = i * stride
      for (let j = 0; j < stride; j++) {
        const value = buffer[index + j]
        component[j] = value
        component[j + stride] = value * dupScale
      }
      push.apply(out, component)
    }
    return out
  },

  mapElement (buffer, elementIndex, stride, map) {
    for (let i = 0, il = buffer.length / stride; i < il; i++) {
      const index = elementIndex + i * stride
      buffer[index] = map(buffer[index], index, i)
    }
    return buffer
  },

  pushElement (buffer, elementIndex, stride) {
    const component = new Array(stride)
    const ai = elementIndex * stride
    for (let i = 0; i < stride; i++) {
      component[i] = buffer[ai + i]
    }
    push.apply(buffer, component)
    return buffer
  },

  unshiftElement (buffer, elementIndex, stride) {
    const component = new Array(stride)
    const ai = elementIndex * stride
    for (let i = 0; i < stride; i++) {
      component[i] = buffer[ai + i]
    }
    unshift.apply(buffer, component)
    return buffer
  }
}

const FLOAT_BYTES = Float32Array.BYTES_PER_ELEMENT

const canvas = document.createElement('canvas')
const regl = createRegl(canvas)
const camera = createCamera(canvas)

// stolen from Nadieh's block //bl.ocks.org/nbremer/5cd07f2cb4ad202a9facfbd5d2bc842e
var colors = ["#2c7bb6", "#00a6ca","#00ccbc","#90eb9d","#ffff8c","#f9d057","#f29e2e","#e76818"];
colorArray = colors.map(function(c) {
  var rgb = d3.rgb(c)
  return [rgb.r/255, rgb.g/255, rgb.b/255, 1]
})
console.log("colors", colorArray)
function setupDrawing(drawing, x, y, z, color) {
  var strokes = drawing.drawing.map(function(s) {
    var points = []
    s[0].forEach(function(xp,i) {
      points.push(xp + x)
      points.push(s[1][i] * -1 + y)
      points.push(z)
    })
//     buffer.pushElement(points, 0, 3)
    buffer.unshiftElement(points, 0, 3)
    points.color = color
    return points;
  })
  return strokes
}

d3.json("https://storage.googleapis.com/fun-data/quickdraw/complex-faces.json", function(err, faces) {
  //var drawing = faces[2];
  var strokes = []
  faces.slice(10,40).forEach(function(face,i) {
    var x = -200 * Math.sin(i*15 * Math.PI/180)
    var y = 200 + 200 * Math.cos(i*15 * Math.PI/180)
    var z = -500 + (i % 10) * 200
    //var color = colorArray[i % colorArray.length]
    var color = colorArray[Math.floor(Math.random()*colorArray.length) % colorArray.length]
    
    strokes = strokes.concat(setupDrawing(face, x, y, z, color))
  })
//   var strokes = setupDrawing(drawing, -300, [0.8, 0.5, 0, 1])

  var draws = strokes.map(function(d) {
    return drawLine(d, d.color) 
  })
  
  regl.frame(({tick}) => {
    regl.clear({
      color: [0.1, 0.1, 0.1, 1],
      depth: 1
    })
    camera.tick()

    draws.forEach(function(d) { d() })
  })

  
})

function drawLine(positions, color) {
  var num = positions.length/3
  var num_total = num + 2
  
  const offset = new Array(num)
  .fill(1)
  .map(function(v, i) { 
    return 2
//     if(i < 2) return 0
//     return (i+90)/POINTS
  })

  const positionsDupSource = new Float32Array(buffer.duplicate(positions, 3))
  const positionsDup = new Float32Array(positionsDupSource)
  const offsetDup = buffer.duplicate(offset, 1, -1)
  const indices = links.lineMesh([], num, 0)

  const positionBuffer = regl.buffer({
    usage: 'dynamic',
    type: 'float',
    length: num_total * 2 * 3 * FLOAT_BYTES
  })
  const offsetBuffer = regl.buffer({
    usage: 'static',
    type: 'float',
    length: num_total * 2 * 1 * FLOAT_BYTES,
    data: offsetDup
  })

  const attributes = {
    prevPosition: {
      buffer: positionBuffer,
      offset: 0,
      stride: FLOAT_BYTES * 3
    },
    currPosition: {
      buffer: positionBuffer,
      offset: FLOAT_BYTES * 3 * 2,
      stride: FLOAT_BYTES * 3
    },
    nextPosition: {
      buffer: positionBuffer,
      offset: FLOAT_BYTES * 3 * 4,
      stride: FLOAT_BYTES * 3
    },
    offsetScale: offsetBuffer
  }

  const uniforms = {
    projection: ({viewportWidth, viewportHeight}) => (
      mat4.perspective([],
        Math.PI / 2,
        viewportWidth / viewportHeight,
        0.01,
        1000)
    ),
    model: mat4.identity([]),
    view: () => camera.view(),
    aspect: ({viewportWidth, viewportHeight}) => (
      viewportWidth / viewportHeight
    ),

    color: color,
    thickness: 1,
    miter: 0
  }

  const elements = regl.elements({
    primitive: 'triangles',
    usage: 'static',
    type: 'uint16',
    data: indices
  })

  // Vertex shader from https://mattdesl.svbtle.com/drawing-lines-is-hard
  // The MIT License (MIT) Copyright (c) 2015 Matt DesLauriers
  const vert = `
  uniform mat4 projection;
  uniform mat4 model;
  uniform mat4 view;
  uniform float aspect;
  uniform float thickness;
  uniform int miter;
  attribute vec3 prevPosition;
  attribute vec3 currPosition;
  attribute vec3 nextPosition;
  attribute float offsetScale;
  void main() {
    vec2 aspectVec = vec2(aspect, 1.0);
    mat4 projViewModel = projection * view * model;
    vec4 prevProjected = projViewModel * vec4(prevPosition, 1.0);
    vec4 currProjected = projViewModel * vec4(currPosition, 1.0);
    vec4 nextProjected = projViewModel * vec4(nextPosition, 1.0);
    // get 2D screen space with W divide and aspect correction
    vec2 prevScreen = prevProjected.xy / prevProjected.w * aspectVec;
    vec2 currScreen = currProjected.xy / currProjected.w * aspectVec;
    vec2 nextScreen = nextProjected.xy / nextProjected.w * aspectVec;
    float len = thickness;
    // starting point uses (next - current)
    vec2 dir = vec2(0.0);
    if (currScreen == prevScreen) {
      dir = normalize(nextScreen - currScreen);
    }
    // ending point uses (current - previous)
    else if (currScreen == nextScreen) {
      dir = normalize(currScreen - prevScreen);
    }
    // somewhere in middle, needs a join
    else {
      // get directions from (C - B) and (B - A)
      vec2 dirA = normalize((currScreen - prevScreen));
      if (miter == 1) {
        vec2 dirB = normalize((nextScreen - currScreen));
        // now compute the miter join normal and length
        vec2 tangent = normalize(dirA + dirB);
        vec2 perp = vec2(-dirA.y, dirA.x);
        vec2 miter = vec2(-tangent.y, tangent.x);
        dir = tangent;
        len = thickness / dot(miter, perp);
      } else {
        dir = dirA;
      }
    }
    vec2 normal = vec2(-dir.y, dir.x) * thickness;
    normal.x /= aspect;
    vec4 offset = vec4(normal * offsetScale, 0.0, 1.0);
    if (currPosition == prevPosition) {
			gl_Position = currProjected + offset;
		} else if(currPosition == nextPosition) {
      gl_Position = prevProjected + offset;
		} else {
      gl_Position = currProjected + offset;
		}
  }`

  const frag = `
  precision mediump float;
  uniform vec4 color;
  void main() {
    gl_FragColor = color;
  }`

  positionBuffer.subdata(positionsDup, 0)

	return regl({
    attributes,
    uniforms,
    elements,
    vert,
    frag
  })  
}




window.addEventListener('resize', fit(canvas), false)
document.body.appendChild(canvas)
</script>
</body>