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
xxxxxxxxxx
<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 https://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>
https://d3js.org/d3.v4.min.js
https://npmcdn.com/regl@1.3.0/dist/regl.js