|
|
|
document.addEventListener('DOMContentLoaded', main);
|
|
|
|
|
|
|
|
const vertex_shader_source = `
|
|
|
|
attribute vec2 a_pos;
|
|
|
|
attribute vec3 a_triangle_color;
|
|
|
|
|
|
|
|
uniform vec2 u_scale;
|
|
|
|
uniform vec2 u_res;
|
|
|
|
uniform vec2 u_translation;
|
|
|
|
uniform int u_layer;
|
|
|
|
|
|
|
|
varying vec3 v_triangle_color;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
vec2 screen01 = (a_pos * u_scale + u_translation) / u_res;
|
|
|
|
vec2 screen02 = screen01 * 2.0;
|
|
|
|
screen02.y = 2.0 - screen02.y;
|
|
|
|
vec2 screen11 = screen02 - 1.0;
|
|
|
|
|
|
|
|
v_triangle_color = a_triangle_color;
|
|
|
|
|
|
|
|
gl_Position = vec4(screen11, u_layer, 1);
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const fragment_shader_source = `
|
|
|
|
precision mediump float;
|
|
|
|
|
|
|
|
uniform vec3 u_color;
|
|
|
|
|
|
|
|
varying vec3 v_triangle_color;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
gl_FragColor = vec4(v_triangle_color, 1);
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
function create_shader(gl, type, source) {
|
|
|
|
const shader = gl.createShader(type);
|
|
|
|
|
|
|
|
gl.shaderSource(shader, source);
|
|
|
|
gl.compileShader(shader);
|
|
|
|
|
|
|
|
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
|
|
return shader;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.error(type, ':', gl.getShaderInfoLog(shader));
|
|
|
|
|
|
|
|
gl.deleteShader(shader);
|
|
|
|
}
|
|
|
|
|
|
|
|
function create_program(gl, vs, fs) {
|
|
|
|
const program = gl.createProgram();
|
|
|
|
|
|
|
|
gl.attachShader(program, vs);
|
|
|
|
gl.attachShader(program, fs);
|
|
|
|
gl.linkProgram(program);
|
|
|
|
|
|
|
|
if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
|
|
return program;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.error('link:', gl.getProgramInfoLog(program));
|
|
|
|
|
|
|
|
gl.deleteProgram(program);
|
|
|
|
}
|
|
|
|
|
|
|
|
function perpendicular(ax, ay, bx, by, width) {
|
|
|
|
// Place points at (stroke_width / 2) distance from the line
|
|
|
|
const dirx = bx - ax;
|
|
|
|
const diry = by - ay;
|
|
|
|
|
|
|
|
let pdirx = diry;
|
|
|
|
let pdiry = -dirx;
|
|
|
|
|
|
|
|
const pdir_norm = Math.sqrt(pdirx * pdirx + pdiry * pdiry);
|
|
|
|
|
|
|
|
pdirx /= pdir_norm;
|
|
|
|
pdiry /= pdir_norm;
|
|
|
|
|
|
|
|
return {
|
|
|
|
'p1': {
|
|
|
|
'x': ax + pdirx * width / 2,
|
|
|
|
'y': ay + pdiry * width / 2,
|
|
|
|
},
|
|
|
|
|
|
|
|
'p2': {
|
|
|
|
'x': ax - pdirx * width / 2,
|
|
|
|
'y': ay - pdiry * width / 2,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const canvas_offset = { 'x': 0, 'y': 0 };
|
|
|
|
let moving = false;
|
|
|
|
let spacedown = false;
|
|
|
|
let drawing = false;
|
|
|
|
let canvas_zoom = 5.0;
|
|
|
|
let current_stroke = [];
|
|
|
|
const bgcolor = { 'r': 0, 'g': 0, 'b': 0 };
|
|
|
|
const stroke_color = { 'r': 0.2, 'g': 0.2, 'b': 0.2 };
|
|
|
|
let debug_draw = true;
|
|
|
|
|
|
|
|
function push_cap_triangle(positions, cap_points, i1, i2, i3) {
|
|
|
|
positions.push(cap_points[i1].x, cap_points[i1].y);
|
|
|
|
positions.push(cap_points[i2].x, cap_points[i2].y);
|
|
|
|
positions.push(cap_points[i3].x, cap_points[i3].y);
|
|
|
|
}
|
|
|
|
|
|
|
|
function push_joint_triangle(positions, joint_points, i1, i2, i3) {
|
|
|
|
positions.push(joint_points[i1].x, joint_points[i1].y);
|
|
|
|
positions.push(joint_points[i2].x, joint_points[i2].y);
|
|
|
|
positions.push(joint_points[i3].x, joint_points[i3].y);
|
|
|
|
}
|
|
|
|
|
|
|
|
function push_cap(positions, pps, p, pnext, stroke_width) {
|
|
|
|
// Rounded cap!
|
|
|
|
const cap_points = [];
|
|
|
|
|
|
|
|
cap_points.push(p); /* not used in positions, added for convenience */
|
|
|
|
cap_points.push(pps.p1);
|
|
|
|
cap_points.push(pps.p2);
|
|
|
|
|
|
|
|
const dy = cap_points[1].y - cap_points[0].y;
|
|
|
|
const dx = cap_points[1].x - cap_points[0].x;
|
|
|
|
|
|
|
|
const down = (pnext.y >= p.y); // = accounts correctly for horizontal lines somehow
|
|
|
|
|
|
|
|
const phi_step = Math.PI / 8;
|
|
|
|
const r = stroke_width / 2;
|
|
|
|
const starting_phi = Math.atan(dy / dx);
|
|
|
|
const sign = down ? -1 : 1;
|
|
|
|
|
|
|
|
for (let i = 1; i <= 7; ++i) {
|
|
|
|
const phi = starting_phi + i * phi_step;
|
|
|
|
const ox = r * Math.cos(phi);
|
|
|
|
const oy = r * Math.sin(phi);
|
|
|
|
const x = cap_points[0].x + sign * ox;
|
|
|
|
const y = cap_points[0].y + sign * oy;
|
|
|
|
cap_points.push({'x': x, 'y': y});
|
|
|
|
}
|
|
|
|
|
|
|
|
push_cap_triangle(positions, cap_points, 2, 1, 6);
|
|
|
|
push_cap_triangle(positions, cap_points, 2, 6, 4);
|
|
|
|
push_cap_triangle(positions, cap_points, 6, 1, 8);
|
|
|
|
push_cap_triangle(positions, cap_points, 2, 4, 3);
|
|
|
|
push_cap_triangle(positions, cap_points, 4, 6, 5);
|
|
|
|
push_cap_triangle(positions, cap_points, 6, 8, 7);
|
|
|
|
push_cap_triangle(positions, cap_points, 8, 1, 9);
|
|
|
|
}
|
|
|
|
|
|
|
|
function push_stroke_positions(stroke, stroke_width, positions) {
|
|
|
|
let last_x1;
|
|
|
|
let last_y1;
|
|
|
|
let last_x2;
|
|
|
|
let last_y2;
|
|
|
|
|
|
|
|
const points = stroke.points;
|
|
|
|
|
|
|
|
if (points.length < 2) {
|
|
|
|
// TODO
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < points.length; ++i) {
|
|
|
|
const px = points[i].x;
|
|
|
|
const py = points[i].y;
|
|
|
|
|
|
|
|
// These might be undefined
|
|
|
|
let nextpx;
|
|
|
|
let nextpy;
|
|
|
|
|
|
|
|
if (i < points.length - 1) {
|
|
|
|
nextpx = points[i + 1].x;
|
|
|
|
nextpy = points[i + 1].y;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i === 0) {
|
|
|
|
const pps = perpendicular(px, py, nextpx, nextpy, stroke_width);
|
|
|
|
|
|
|
|
last_x1 = pps.p1.x;
|
|
|
|
last_y1 = pps.p1.y;
|
|
|
|
last_x2 = pps.p2.x;
|
|
|
|
last_y2 = pps.p2.y;
|
|
|
|
|
|
|
|
push_cap(positions, pps, points[0], points[1], stroke_width);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Place points at (stroke_width / 2) distance from the line
|
|
|
|
const prevpx = points[i - 1].x;
|
|
|
|
const prevpy = points[i - 1].y;
|
|
|
|
|
|
|
|
let x1;
|
|
|
|
let y1;
|
|
|
|
let x2;
|
|
|
|
let y2;
|
|
|
|
|
|
|
|
if (i < points.length - 1) {
|
|
|
|
let d1x = px - prevpx;
|
|
|
|
let d1y = py - prevpy;
|
|
|
|
|
|
|
|
let d2x = px - nextpx;
|
|
|
|
let d2y = py - nextpy;
|
|
|
|
|
|
|
|
// Construct "inner" sides and find their intersection point
|
|
|
|
let perp1x = d1y;
|
|
|
|
let perp1y = -d1x;
|
|
|
|
|
|
|
|
const perpnorm1 = Math.sqrt(perp1x * perp1x + perp1y * perp1y);
|
|
|
|
|
|
|
|
perp1x /= perpnorm1;
|
|
|
|
perp1y /= perpnorm1;
|
|
|
|
|
|
|
|
// Starting point for first "inner" line
|
|
|
|
const inner1x = prevpx + perp1x * stroke_width / 2;
|
|
|
|
const inner1y = prevpy + perp1y * stroke_width / 2;
|
|
|
|
|
|
|
|
let perp2x = -d2y;
|
|
|
|
let perp2y = d2x;
|
|
|
|
|
|
|
|
const perpnorm2 = Math.sqrt(perp2x * perp2x + perp2y * perp2y);
|
|
|
|
|
|
|
|
perp2x /= perpnorm2;
|
|
|
|
perp2y /= perpnorm2;
|
|
|
|
|
|
|
|
// Starting point for second "inner" line
|
|
|
|
const inner2x = nextpx + perp2x * stroke_width / 2;
|
|
|
|
const inner2y = nextpy + perp2y * stroke_width / 2;
|
|
|
|
|
|
|
|
const innerintt2 = (d1x * (inner1y - inner2y) + d1y * (inner2x - inner1x)) / (d1x * d2y - d1y * d2x);
|
|
|
|
const innerint1x = inner2x + innerintt2 * d2x;
|
|
|
|
const innerint1y = inner2y + innerintt2 * d2y;
|
|
|
|
|
|
|
|
const sanityt1 = (inner2x + innerintt2 * d2x - inner1x) / d1x;
|
|
|
|
const sanity1x = inner1x + sanityt1 * d1x;
|
|
|
|
const sanity1y = inner1y + sanityt1 * d1y;
|
|
|
|
|
|
|
|
// Starting point for first "outer" line
|
|
|
|
const outer1x = prevpx - perp1x * stroke_width / 2;
|
|
|
|
const outer1y = prevpy - perp1y * stroke_width / 2;
|
|
|
|
|
|
|
|
// Starting point for second "outer" line
|
|
|
|
const outer2x = nextpx - perp2x * stroke_width / 2;
|
|
|
|
const outer2y = nextpy - perp2y * stroke_width / 2;
|
|
|
|
|
|
|
|
const outerintt2 = (d1x * (outer1y - outer2y) + d1y * (outer2x - outer1x)) / (d1x * d2y - d1y * d2x);
|
|
|
|
const outerint1x = outer2x + outerintt2 * d2x;
|
|
|
|
const outerint1y = outer2y + outerintt2 * d2y;
|
|
|
|
|
|
|
|
x1 = innerint1x;
|
|
|
|
y1 = innerint1y;
|
|
|
|
x2 = outerint1x;
|
|
|
|
y2 = outerint1y;
|
|
|
|
|
|
|
|
// If we are turning left, then we should place the circle on the right, and vice versa
|
|
|
|
const turn_left = point_right_of_line(points[i - 1], points[i + 1], points[i]);
|
|
|
|
// if (turn_left) {
|
|
|
|
const s1x = px - perp2x * stroke_width / 2;
|
|
|
|
const s1y = py - perp2y * stroke_width / 2;
|
|
|
|
|
|
|
|
const s2x = px - perp1x * stroke_width / 2;
|
|
|
|
const s2y = py - perp1y * stroke_width / 2;
|
|
|
|
|
|
|
|
let s12prod = perp1x * perp2x + perp1y * perp2y;
|
|
|
|
let angle = Math.acos(s12prod);
|
|
|
|
|
|
|
|
// Generate circular segment
|
|
|
|
const npoints = Math.ceil(angle / Math.PI * 7);
|
|
|
|
|
|
|
|
|
|
|
|
if (npoints > 1) {
|
|
|
|
const joint_points = [];
|
|
|
|
|
|
|
|
joint_points.push(points[i]);
|
|
|
|
joint_points.push({'x': s2x, 'y': s2y});
|
|
|
|
joint_points.push({'x': s1x, 'y': s1y});
|
|
|
|
|
|
|
|
const pnext = points[i + 1];
|
|
|
|
const p = points[i];
|
|
|
|
const down = (pnext.y > p.y);
|
|
|
|
const phi_step = angle / (npoints + 1);
|
|
|
|
const r = stroke_width / 2;
|
|
|
|
const starting_phi = Math.atan(perp2y / perp2x);
|
|
|
|
const sign = down ? -1 : 1;
|
|
|
|
|
|
|
|
for (let i = 1; i <= npoints; ++i) {
|
|
|
|
const phi = starting_phi + i * phi_step;
|
|
|
|
const ox = r * Math.cos(phi);
|
|
|
|
const oy = r * Math.sin(phi);
|
|
|
|
const x = p.x + sign * ox;
|
|
|
|
const y = p.y + sign * oy;
|
|
|
|
joint_points.push({'x': x, 'y': y});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rectangular segment up to here
|
|
|
|
if (innerintt2 > 0) {
|
|
|
|
positions.push(last_x1, last_y1);
|
|
|
|
positions.push(innerint1x - perp1x * stroke_width, innerint1y - perp1y * stroke_width);
|
|
|
|
positions.push(last_x2, last_y2);
|
|
|
|
|
|
|
|
positions.push(last_x1, last_y1);
|
|
|
|
positions.push(innerint1x - perp1x * stroke_width, innerint1y - perp1y * stroke_width);
|
|
|
|
positions.push(innerint1x, innerint1y);
|
|
|
|
|
|
|
|
// Four triangles to cover the non-circle part of the join
|
|
|
|
positions.push(innerint1x, innerint1y);
|
|
|
|
positions.push(px, py);
|
|
|
|
positions.push(innerint1x - perp1x * stroke_width, innerint1y - perp1y * stroke_width);
|
|
|
|
|
|
|
|
positions.push(innerint1x - perp1x * stroke_width, innerint1y - perp1y * stroke_width);
|
|
|
|
positions.push(px, py);
|
|
|
|
positions.push(s2x, s2y);
|
|
|
|
|
|
|
|
positions.push(innerint1x - perp2x * stroke_width, innerint1y - perp2y * stroke_width);
|
|
|
|
positions.push(px, py);
|
|
|
|
positions.push(innerint1x, innerint1y);
|
|
|
|
|
|
|
|
positions.push(innerint1x - perp2x * stroke_width, innerint1y - perp2y * stroke_width);
|
|
|
|
positions.push(px, py);
|
|
|
|
positions.push(s1x, s1y);
|
|
|
|
|
|
|
|
|
|
|
|
last_x1 = innerint1x;
|
|
|
|
last_y1 = innerint1y;
|
|
|
|
|
|
|
|
last_x2 = innerint1x - perp2x * stroke_width;
|
|
|
|
last_y2 = innerint1y - perp2y * stroke_width;
|
|
|
|
} else {
|
|
|
|
last_x1 = nextpx + perp2x * stroke_width / 2;
|
|
|
|
last_y1 = nextpy + perp2y * stroke_width / 2;
|
|
|
|
|
|
|
|
last_x2 = nextpx - perp2x * stroke_width / 2;
|
|
|
|
last_y2 = nextpy - perp2y * stroke_width / 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// push_joint_triangle(positions, joint_points, 0, 2, npoints + 2);
|
|
|
|
for (let i = 0; i < npoints; ++i) {
|
|
|
|
push_joint_triangle(positions, joint_points, 0, i + 3, i + 2);
|
|
|
|
}
|
|
|
|
push_joint_triangle(positions, joint_points, 0, 2 + npoints, 1);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
if (innerintt2 > 0) {
|
|
|
|
// Rectangular segment up to interpolated perpendicular
|
|
|
|
let perp3x = (perp1x + perp2x) / 2.0;
|
|
|
|
let perp3y = (perp1y + perp2y) / 2.0;
|
|
|
|
|
|
|
|
const perp3norm = Math.sqrt(perp3x * perp3x + perp3y * perp3y);
|
|
|
|
|
|
|
|
perp3x /= perp3norm;
|
|
|
|
perp3y /= perp3norm;
|
|
|
|
|
|
|
|
positions.push(last_x1, last_y1);
|
|
|
|
positions.push(px - perp3x * stroke_width / 2, py - perp3y * stroke_width / 2);
|
|
|
|
positions.push(last_x2, last_y2);
|
|
|
|
|
|
|
|
positions.push(px - perp3x * stroke_width / 2, py - perp3y * stroke_width / 2);
|
|
|
|
positions.push(px + perp3x * stroke_width / 2, py + perp3y * stroke_width / 2);
|
|
|
|
positions.push(last_x1, last_y1);
|
|
|
|
|
|
|
|
last_x1 = px + perp3x * stroke_width / 2;
|
|
|
|
last_y1 = py + perp3y * stroke_width / 2;
|
|
|
|
|
|
|
|
last_x2 = px - perp3x * stroke_width / 2;
|
|
|
|
last_y2 = py - perp3y * stroke_width / 2;
|
|
|
|
} else {
|
|
|
|
last_x1 = nextpx + perp2x * stroke_width / 2;
|
|
|
|
last_y1 = nextpy + perp2y * stroke_width / 2;
|
|
|
|
|
|
|
|
last_x2 = nextpx - perp2x * stroke_width / 2;
|
|
|
|
last_y2 = nextpy - perp2y * stroke_width / 2;
|
|
|
|
// last_x1 = px + perp3x * stroke_width / 2;
|
|
|
|
// last_y1 = py + perp3y * stroke_width / 2;
|
|
|
|
|
|
|
|
// last_x2 = px - perp3x * stroke_width / 2;
|
|
|
|
// last_y2 = py - perp3y * stroke_width / 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// }
|
|
|
|
} else {
|
|
|
|
const pps = perpendicular(px, py, prevpx, prevpy, stroke_width);
|
|
|
|
|
|
|
|
x1 = pps.p2.x;
|
|
|
|
y1 = pps.p2.y;
|
|
|
|
x2 = pps.p1.x;
|
|
|
|
y2 = pps.p1.y;
|
|
|
|
|
|
|
|
push_cap(positions, pps, points[points.length - 1], points[points.length - 2], stroke_width);
|
|
|
|
|
|
|
|
positions.push(last_x1, last_y1);
|
|
|
|
positions.push(x2, y2);
|
|
|
|
positions.push(last_x2, last_y2);
|
|
|
|
|
|
|
|
positions.push(last_x1, last_y1);
|
|
|
|
positions.push(x1, y1);
|
|
|
|
positions.push(x2, y2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function draw(gl, program, locations, buffers, strokes) {
|
|
|
|
const width = window.innerWidth;
|
|
|
|
const height = window.innerHeight;
|
|
|
|
|
|
|
|
if (gl.canvas.width !== width || gl.canvas.height !== height) {
|
|
|
|
gl.canvas.width = width;
|
|
|
|
gl.canvas.height = height;
|
|
|
|
gl.viewport(0, 0, width, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
gl.clearColor(bgcolor.r, bgcolor.g, bgcolor.b, 1);
|
|
|
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
|
|
gl.useProgram(program);
|
|
|
|
gl.enableVertexAttribArray(locations['a_pos']);
|
|
|
|
gl.enableVertexAttribArray(locations['a_triangle_color']);
|
|
|
|
|
|
|
|
gl.uniform2f(locations['u_res'], width, height);
|
|
|
|
gl.uniform2f(locations['u_scale'], canvas_zoom, canvas_zoom);
|
|
|
|
gl.uniform2f(locations['u_translation'], canvas_offset.x, canvas_offset.y);
|
|
|
|
|
|
|
|
const positions = [];
|
|
|
|
const colors = [];
|
|
|
|
const stroke_width = 10;
|
|
|
|
|
|
|
|
for (const stroke of strokes) {
|
|
|
|
push_stroke_positions(stroke, stroke_width, positions);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (current_stroke.length > 0) {
|
|
|
|
push_stroke_positions({'points': current_stroke}, stroke_width, positions);
|
|
|
|
}
|
|
|
|
|
|
|
|
const npoints = positions.length / 2;
|
|
|
|
|
|
|
|
for (let i = 0; i < npoints; i += 3) {
|
|
|
|
if (!debug_draw) {
|
|
|
|
positions.push(0, 0, 0);
|
|
|
|
positions.push(0, 0, 0);
|
|
|
|
positions.push(0, 0, 0);
|
|
|
|
} else {
|
|
|
|
let r = (i * 761257125 % 255) / 255.0;
|
|
|
|
let g = (i * 871295862 % 255) / 255.0;
|
|
|
|
let b = (i * 287238767 % 255) / 255.0;
|
|
|
|
|
|
|
|
if (r < 0.3) r = 0.3;
|
|
|
|
if (g < 0.3) g = 0.3;
|
|
|
|
if (b < 0.3) b = 0.3;
|
|
|
|
|
|
|
|
positions.push(r, g, b);
|
|
|
|
positions.push(r, g, b);
|
|
|
|
positions.push(r, g, b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const posf32 = new Float32Array(positions);
|
|
|
|
const pointbytes = 4 * npoints * 2;
|
|
|
|
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['in']);
|
|
|
|
gl.bufferData(gl.ARRAY_BUFFER, posf32.byteLength, gl.STATIC_DRAW);
|
|
|
|
gl.bufferSubData(gl.ARRAY_BUFFER, 0, posf32.slice(0, npoints * 2));
|
|
|
|
gl.bufferSubData(gl.ARRAY_BUFFER, pointbytes, posf32.slice(npoints * 2));
|
|
|
|
|
|
|
|
{
|
|
|
|
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
|
|
|
|
const size = 2; // 2 components per iteration
|
|
|
|
const type = gl.FLOAT; // the data is 32bit floats
|
|
|
|
const normalize = false; // don't normalize the data
|
|
|
|
const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
|
|
|
|
const offset = 0; // start at the beginning of the buffer
|
|
|
|
gl.vertexAttribPointer(locations['a_pos'], size, type, normalize, stride, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
|
|
|
|
const size = 3; // 3 components per iteration
|
|
|
|
const type = gl.FLOAT; // the data is 32bit floats
|
|
|
|
const normalize = false; // don't normalize the data
|
|
|
|
const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
|
|
|
|
const offset = pointbytes; // start at the beginning of the buffer
|
|
|
|
gl.vertexAttribPointer(locations['a_triangle_color'], size, type, normalize, stride, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const offset = 0;
|
|
|
|
const count = npoints;
|
|
|
|
gl.uniform3f(locations['u_color'], stroke_color.r, stroke_color.g, stroke_color.b);
|
|
|
|
gl.uniform1i(locations['u_layer'], 0);
|
|
|
|
gl.drawArrays(gl.TRIANGLES, offset, count);
|
|
|
|
}
|
|
|
|
|
|
|
|
window.requestAnimationFrame(() => draw(gl, program, locations, buffers, strokes));
|
|
|
|
}
|
|
|
|
|
|
|
|
function main() {
|
|
|
|
const canvas = document.querySelector('#c');
|
|
|
|
const gl = canvas.getContext('webgl');
|
|
|
|
|
|
|
|
if (!gl) {
|
|
|
|
console.error('FUCK!')
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const vertex_shader = create_shader(gl, gl.VERTEX_SHADER, vertex_shader_source);
|
|
|
|
const fragment_shader = create_shader(gl, gl.FRAGMENT_SHADER, fragment_shader_source);
|
|
|
|
const program = create_program(gl, vertex_shader, fragment_shader)
|
|
|
|
|
|
|
|
const locations = {};
|
|
|
|
const buffers = {};
|
|
|
|
|
|
|
|
locations['a_pos'] = gl.getAttribLocation(program, 'a_pos');
|
|
|
|
locations['a_triangle_color'] = gl.getAttribLocation(program, 'a_triangle_color');
|
|
|
|
locations['u_res'] = gl.getUniformLocation(program, 'u_res');
|
|
|
|
locations['u_scale'] = gl.getUniformLocation(program, 'u_scale');
|
|
|
|
locations['u_translation'] = gl.getUniformLocation(program, 'u_translation');
|
|
|
|
locations['u_color'] = gl.getUniformLocation(program, 'u_color');
|
|
|
|
locations['u_layer'] = gl.getUniformLocation(program, 'u_layer');
|
|
|
|
|
|
|
|
buffers['in'] = gl.createBuffer();
|
|
|
|
|
|
|
|
const strokes = [
|
|
|
|
{
|
|
|
|
'points': [
|
|
|
|
{'x': 100, 'y': 100},
|
|
|
|
{'x': 105, 'y': 500},
|
|
|
|
{'x': 108, 'y': 140},
|
|
|
|
]
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
|
|
|
window.addEventListener('keydown', (e) => {
|
|
|
|
if (e.code === 'Space') {
|
|
|
|
spacedown = true;
|
|
|
|
} else if (e.code === 'KeyD') {
|
|
|
|
debug_draw = !debug_draw;
|
|
|
|
if (debug_draw) {
|
|
|
|
stroke_color.r = 0.2;
|
|
|
|
stroke_color.g = 0.2;
|
|
|
|
stroke_color.b = 0.2;
|
|
|
|
bgcolor.r = 0;
|
|
|
|
bgcolor.g = 0;
|
|
|
|
bgcolor.b = 0;
|
|
|
|
} else {
|
|
|
|
stroke_color.r = 0;
|
|
|
|
stroke_color.g = 0;
|
|
|
|
stroke_color.b = 0;
|
|
|
|
bgcolor.r = 1;
|
|
|
|
bgcolor.g = 1;
|
|
|
|
bgcolor.b = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
window.addEventListener('keyup', (e) => {
|
|
|
|
if (e.code === 'Space') {
|
|
|
|
spacedown = false;
|
|
|
|
moving = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
canvas.addEventListener('mousedown', (e) => {
|
|
|
|
if (spacedown) {
|
|
|
|
moving = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const x = cursor_x = (e.clientX - canvas_offset.x) / canvas_zoom;
|
|
|
|
const y = cursor_y = (e.clientY - canvas_offset.y) / canvas_zoom;
|
|
|
|
|
|
|
|
current_stroke.length = 0;
|
|
|
|
current_stroke.push({'x': x, 'y': y});
|
|
|
|
drawing = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
canvas.addEventListener('mousemove', (e) => {
|
|
|
|
if (moving) {
|
|
|
|
canvas_offset.x += e.movementX;
|
|
|
|
canvas_offset.y += e.movementY;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (drawing) {
|
|
|
|
const x = cursor_x = (e.clientX - canvas_offset.x) / canvas_zoom;
|
|
|
|
const y = cursor_y = (e.clientY - canvas_offset.y) / canvas_zoom;
|
|
|
|
|
|
|
|
current_stroke.push({'x': x, 'y': y});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
canvas.addEventListener('mouseup', (e) => {
|
|
|
|
if (spacedown) {
|
|
|
|
moving = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (drawing) {
|
|
|
|
strokes.push({'points': process_stroke(current_stroke)});
|
|
|
|
current_stroke.length = 0;
|
|
|
|
drawing = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
canvas.addEventListener('wheel', (e) => {
|
|
|
|
const x = Math.round((e.clientX - canvas_offset.x) / canvas_zoom);
|
|
|
|
const y = Math.round((e.clientY - canvas_offset.y) / canvas_zoom);
|
|
|
|
|
|
|
|
const dz = (e.deltaY < 0 ? 0.1 : -0.1);
|
|
|
|
const old_zoom = canvas_zoom;
|
|
|
|
|
|
|
|
canvas_zoom *= (1.0 + dz);
|
|
|
|
|
|
|
|
if (canvas_zoom > 100.0) {
|
|
|
|
canvas_zoom = old_zoom;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (canvas_zoom < 0.2) {
|
|
|
|
canvas_zoom = old_zoom;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const zoom_offset_x = Math.round((dz * old_zoom) * x);
|
|
|
|
const zoom_offset_y = Math.round((dz * old_zoom) * y);
|
|
|
|
|
|
|
|
canvas_offset.x -= zoom_offset_x;
|
|
|
|
canvas_offset.y -= zoom_offset_y;
|
|
|
|
});
|
|
|
|
|
|
|
|
window.requestAnimationFrame(() => draw(gl, program, locations, buffers, strokes));
|
|
|
|
}
|