Browse Source

Caps and joints via fragment shader

infinite
A.Olokhtonov 2 years ago
parent
commit
8557c5d47e
  1. 9
      client/index.js
  2. 4
      client/tools.js
  3. 63
      client/webgl_draw.js
  4. 82
      client/webgl_geometry.js
  5. 1
      client/webgl_listeners.js
  6. 58
      client/webgl_shaders.js

9
client/index.js

@ -113,14 +113,23 @@ function main() { @@ -113,14 +113,23 @@ function main() {
'dynamic_positions': {},
'dynamic_colors': {},
'dynamic_circle_positions': {},
'dynamic_circle_colors': {},
'quad_positions': [],
'quad_texcoords': [],
'static_positions': [],
'static_colors': [],
'static_circle_positions': [],
'static_circle_colors': [],
'static_positions_f32': new Float32Array(0),
'dynamic_positions_f32': new Float32Array(0),
'static_colors_u8': new Uint8Array(0),
'dynamic_colors_u8': new Uint8Array(0),
'static_circle_positions_f32': new Float32Array(0),
'dynamic_circle_positions_f32': new Float32Array(0),
'static_circle_colors_u8': new Uint8Array(0),
'dynamic_circle_colors_u8': new Uint8Array(0),
'quad_positions_f32': new Float32Array(0),
'quad_texcoords_f32': new Float32Array(0),
'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0},

4
client/tools.js

@ -1,6 +1,10 @@ @@ -1,6 +1,10 @@
function switch_tool(state, item) {
const tool = item.getAttribute('data-tool');
if (tool === 'undo') {
return;
}
if (state.tools.active_element) {
state.tools.active_element.classList.remove('active');
}

63
client/webgl_draw.js

@ -32,6 +32,7 @@ function draw(state, context) { @@ -32,6 +32,7 @@ function draw(state, context) {
gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom);
gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y);
gl.uniform1i(locations['u_layer'], 0);
gl.uniform1i(locations['u_texture'], 0);
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_pos']);
gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 0, 0);
@ -64,7 +65,7 @@ function draw(state, context) { @@ -64,7 +65,7 @@ function draw(state, context) {
gl.drawArrays(gl.TRIANGLES, active_image_index * 6, 6);
}
// Draw strokes
// Strokes
locations = context.locations['stroke'];
buffers = context.buffers['stroke'];
@ -95,4 +96,64 @@ function draw(state, context) { @@ -95,4 +96,64 @@ function draw(state, context) {
gl.bufferSubData(gl.ARRAY_BUFFER, context.static_colors_u8.byteLength, context.dynamic_colors_u8);
gl.drawArrays(gl.TRIANGLES, 0, total_point_count);
// Circles
locations = context.locations['circle'];
buffers = context.buffers['circle'];
gl.useProgram(context.programs['circle']);
gl.enableVertexAttribArray(locations['a_pos']);
gl.enableVertexAttribArray(locations['a_texcoord']);
gl.enableVertexAttribArray(locations['a_color']);
gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height);
gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom);
gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y);
gl.uniform1i(locations['u_layer'], 1);
const total_circle_pos_size = context.static_circle_positions_f32.byteLength + context.dynamic_circle_positions_f32.byteLength;
const total_circle_color_size = context.static_circle_colors_u8.byteLength + context.dynamic_circle_colors_u8.byteLength;
const total_circle_point_count = (context.static_circle_positions.length + total_dynamic_circle_positions(context)) / 2;
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_pos']);
gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, total_circle_pos_size, gl.DYNAMIC_DRAW);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, context.static_circle_positions_f32);
gl.bufferSubData(gl.ARRAY_BUFFER, context.static_circle_positions_f32.byteLength, context.dynamic_circle_positions_f32);
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_color']);
gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, total_circle_color_size, gl.DYNAMIC_DRAW);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, context.static_circle_colors_u8);
gl.bufferSubData(gl.ARRAY_BUFFER, context.static_circle_colors_u8.byteLength, context.dynamic_circle_colors_u8);
// TODO: move this somewhere?
const circle_quad_uv = new Float32Array(total_circle_point_count * 2);
for (let quad = 0; quad < total_circle_point_count / 6; ++quad) {
circle_quad_uv[quad * 12 + 0] = 0;
circle_quad_uv[quad * 12 + 1] = 0;
circle_quad_uv[quad * 12 + 2] = 0;
circle_quad_uv[quad * 12 + 3] = 1;
circle_quad_uv[quad * 12 + 4] = 1;
circle_quad_uv[quad * 12 + 5] = 0;
circle_quad_uv[quad * 12 + 6] = 1;
circle_quad_uv[quad * 12 + 7] = 1;
circle_quad_uv[quad * 12 + 8] = 1;
circle_quad_uv[quad * 12 + 9] = 0;
circle_quad_uv[quad * 12 + 10] = 0;
circle_quad_uv[quad * 12 + 11] = 1;
}
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_texcoord']);
gl.vertexAttribPointer(locations['a_texcoord'], 2, gl.FLOAT, false, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, circle_quad_uv, gl.DYNAMIC_DRAW);
gl.drawArrays(gl.TRIANGLES, 0, total_circle_point_count);
}

82
client/webgl_geometry.js

@ -1,21 +1,13 @@ @@ -1,21 +1,13 @@
function push_circle_at(positions, cl, r, g, b, c, o) {
positions.push(c.x + o[0].x, c.y + o[0].y, c.x + o[4].x, c.y + o[4].y, c.x + o[8].x, c.y + o[8].y);
positions.push(c.x + o[4].x, c.y + o[4].y, c.x + o[0].x, c.y + o[0].y, c.x + o[2].x, c.y + o[2].y);
positions.push(c.x + o[8].x, c.y + o[8].y, c.x + o[4].x, c.y + o[4].y, c.x + o[6].x, c.y + o[6].y);
positions.push(c.x + o[0].x, c.y + o[0].y, c.x + o[8].x, c.y + o[8].y, c.x + o[10].x, c.y + o[10].y);
positions.push(c.x + o[2].x, c.y + o[2].y, c.x + o[0].x, c.y + o[0].y, c.x + o[1].x, c.y + o[1].y);
positions.push(c.x + o[4].x, c.y + o[4].y, c.x + o[2].x, c.y + o[2].y, c.x + o[3].x, c.y + o[3].y);
positions.push(c.x + o[6].x, c.y + o[6].y, c.x + o[4].x, c.y + o[4].y, c.x + o[5].x, c.y + o[5].y);
positions.push(c.x + o[8].x, c.y + o[8].y, c.x + o[6].x, c.y + o[6].y, c.x + o[7].x, c.y + o[7].y);
positions.push(c.x + o[10].x, c.y + o[10].y, c.x + o[8].x, c.y + o[8].y, c.x + o[9].x, c.y + o[9].y);
positions.push(c.x + o[0].x, c.y + o[0].y, c.x + o[10].x, c.y + o[10].y, c.x + o[11].x, c.y + o[11].y);
for (let i = 0; i < 3 * 10; ++i) {
function push_circle_at(circle_positions, cl, r, g, b, c, radius) {
circle_positions.push(c.x - radius, c.y - radius, c.x - radius, c.y + radius, c.x + radius, c.y - radius);
circle_positions.push(c.x + radius, c.y + radius, c.x + radius, c.y - radius, c.x - radius, c.y + radius);
for (let i = 0; i < 6; ++i) {
cl.push(r, g, b);
}
}
function push_stroke(state, stroke, positions, colors) {
function push_stroke(state, stroke, positions, colors, circle_positions, circle_colors) {
const starting_length = positions.length;
const stroke_width = stroke.width;
const points = stroke.points;
@ -35,15 +27,6 @@ function push_stroke(state, stroke, positions, colors) { @@ -35,15 +27,6 @@ function push_stroke(state, stroke, positions, colors) {
const POINTS = 12;
const phi_step = 2 * Math.PI / POINTS;
const circle_offsets = [];
for (let i = 0; i < POINTS; ++i) {
const phi = phi_step * i;
const ox = stroke_width / 2 * Math.cos(phi);
const oy = stroke_width / 2 * Math.sin(phi);
circle_offsets.push({'x': ox, 'y': oy});
}
for (let i = 0; i < points.length - 1; ++i) {
const px = points[i].x;
const py = points[i].y;
@ -85,11 +68,10 @@ function push_stroke(state, stroke, positions, colors) { @@ -85,11 +68,10 @@ function push_stroke(state, stroke, positions, colors) {
// "poke out" of the rectangle
const angle = Math.atan(Math.abs(s3x - s4x), Math.abs(s3y - s4y));
push_circle_at(positions, colors, r, g, b, points[i], circle_offsets);
push_circle_at(circle_positions, circle_colors, r, g, b, points[i], stroke_width / 2);
}
// TODO: angle
push_circle_at(positions, colors, r, g, b, points[points.length - 1], circle_offsets);
push_circle_at(circle_positions, circle_colors, r, g, b, points[points.length - 1], stroke_width / 2);
stroke.popcount = positions.length - starting_length;
}
@ -127,17 +109,25 @@ function get_static_stroke(state) { @@ -127,17 +109,25 @@ function get_static_stroke(state) {
function add_static_stroke(state, context, stroke, relax = false) {
if (!state.online || !stroke) return;
push_stroke(state, stroke, context.static_positions, context.static_colors);
push_stroke(state, stroke, context.static_positions, context.static_colors, context.static_circle_positions, context.static_circle_colors);
if (!relax) {
// TODO: incremental
context.static_positions_f32 = new Float32Array(context.static_positions);
context.static_colors_u8 = new Uint8Array(context.static_colors);
context.static_circle_positions_f32 = new Float32Array(context.static_circle_positions);
context.static_circle_colors_u8 = new Uint8Array(context.static_circle_colors);
}
}
function recompute_static_data(context) {
context.static_positions_f32 = new Float32Array(context.static_positions);
context.static_colors_u8 = new Uint8Array(context.static_colors);
context.static_circle_positions_f32 = new Float32Array(context.static_circle_positions);
context.static_circle_colors_u8 = new Uint8Array(context.static_circle_colors);
}
function total_dynamic_positions(context) {
@ -150,17 +140,33 @@ function total_dynamic_positions(context) { @@ -150,17 +140,33 @@ function total_dynamic_positions(context) {
return total_dynamic_length;
}
function total_dynamic_circle_positions(context) {
let total_dynamic_length = 0;
for (const player_id in context.dynamic_circle_positions) {
total_dynamic_length += context.dynamic_circle_positions[player_id].length;
}
return total_dynamic_length;
}
function recompute_dynamic_data(state, context) {
const total_dynamic_length = total_dynamic_positions(context);
const total_dynamic_circles_length = total_dynamic_circle_positions(context);
context.dynamic_positions_f32 = new Float32Array(total_dynamic_length);
context.dynamic_colors_u8 = new Uint8Array(total_dynamic_length / 2 * 3);
context.dynamic_circle_positions_f32 = new Float32Array(total_dynamic_circles_length);
context.dynamic_circle_colors_u8 = new Uint8Array(total_dynamic_circles_length / 2 * 3);
let at = 0;
let at_circle = 0;
for (const player_id in context.dynamic_positions) {
context.dynamic_positions_f32.set(context.dynamic_positions[player_id], at);
context.dynamic_circle_positions_f32.set(context.dynamic_circle_positions[player_id], at_circle);
const color_u32 = state.players[player_id].color;
const r = (color_u32 >> 16) & 0xFF;
@ -173,7 +179,14 @@ function recompute_dynamic_data(state, context) { @@ -173,7 +179,14 @@ function recompute_dynamic_data(state, context) {
context.dynamic_colors_u8[at / 2 * 3 + i * 3 + 2] = b;
}
for (let i = 0; i < context.dynamic_circle_positions[player_id].length; ++i) {
context.dynamic_circle_colors_u8[at_circle / 2 * 3 + i * 3 + 0] = r;
context.dynamic_circle_colors_u8[at_circle / 2 * 3 + i * 3 + 1] = g;
context.dynamic_circle_colors_u8[at_circle / 2 * 3 + i * 3 + 2] = b;
}
at += context.dynamic_positions[player_id].length;
at_circle += context.dynamic_circle_positions[player_id].length;
}
}
@ -189,6 +202,9 @@ function update_dynamic_stroke(state, context, player_id, point) { @@ -189,6 +202,9 @@ function update_dynamic_stroke(state, context, player_id, point) {
context.dynamic_positions[player_id] = [];
context.dynamic_colors[player_id] = [];
context.dynamic_circle_positions[player_id] = [];
context.dynamic_circle_colors[player_id] = [];
}
state.current_strokes[player_id].color = state.players[player_id].color;
@ -198,8 +214,15 @@ function update_dynamic_stroke(state, context, player_id, point) { @@ -198,8 +214,15 @@ function update_dynamic_stroke(state, context, player_id, point) {
context.dynamic_positions[player_id].length = 0;
context.dynamic_colors[player_id].length = 0;
context.dynamic_circle_positions[player_id].length = 0;
context.dynamic_circle_colors[player_id].length = 0;
state.current_strokes[player_id].points.push(point);
push_stroke(state, state.current_strokes[player_id], context.dynamic_positions[player_id], context.dynamic_colors[player_id]);
push_stroke(state, state.current_strokes[player_id],
context.dynamic_positions[player_id], context.dynamic_colors[player_id],
context.dynamic_circle_positions[player_id], context.dynamic_circle_colors[player_id]
);
recompute_dynamic_data(state, context);
}
@ -212,6 +235,7 @@ function clear_dynamic_stroke(state, context, player_id) { @@ -212,6 +235,7 @@ function clear_dynamic_stroke(state, context, player_id) {
state.current_strokes[player_id].color = state.players[state.me].color;
state.current_strokes[player_id].width = state.players[state.me].width;
context.dynamic_positions[player_id].length = 0;
context.dynamic_circle_positions[player_id].length = 0;
recompute_dynamic_data(state, context);
}
}

1
client/webgl_listeners.js

@ -283,7 +283,6 @@ function touchmove(e, state, context) { @@ -283,7 +283,6 @@ function touchmove(e, state, context) {
if (state.touch.buffered.length > 0) {
clear_dynamic_stroke(state, context, state.me);
// NEXT: BUG: can't see these on other clients!!
for (const p of state.touch.buffered) {
update_dynamic_stroke(state, context, state.me, p);
fire_event(state, predraw_event(p.x, p.y));

58
client/webgl_shaders.js

@ -67,6 +67,43 @@ const tquad_fs_src = ` @@ -67,6 +67,43 @@ const tquad_fs_src = `
}
`;
const tcircle_vs_src = `
attribute vec2 a_pos;
attribute vec2 a_texcoord;
attribute vec3 a_color;
uniform vec2 u_scale;
uniform vec2 u_res;
uniform vec2 u_translation;
uniform int u_layer;
varying vec2 v_texcoord;
varying vec3 v_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_texcoord = a_texcoord * 2.0 - 1.0;
v_color = a_color;
gl_Position = vec4(screen11, u_layer, 1);
}
`;
const tcircle_fs_src = `
precision mediump float;
varying vec2 v_texcoord;
varying vec3 v_color;
void main() {
float val = smoothstep(1.0, 0.995, length(v_texcoord));
gl_FragColor = vec4(vec3(val * v_color), val);
// gl_FragColor = vec4(v_texcoord, 0, 1);
}
`;
function init_webgl(state, context) {
context.canvas = document.querySelector('#c');
context.gl = context.canvas.getContext('webgl', {
@ -86,8 +123,12 @@ function init_webgl(state, context) { @@ -86,8 +123,12 @@ function init_webgl(state, context) {
const quad_vs = create_shader(gl, gl.VERTEX_SHADER, tquad_vs_src);
const quad_fs = create_shader(gl, gl.FRAGMENT_SHADER, tquad_fs_src);
const circle_vs = create_shader(gl, gl.VERTEX_SHADER, tcircle_vs_src);
const circle_fs = create_shader(gl, gl.FRAGMENT_SHADER, tcircle_fs_src);
context.programs['stroke'] = create_program(gl, stroke_vs, stroke_fs);
context.programs['quad'] = create_program(gl, quad_vs, quad_fs);
context.programs['circle'] = create_program(gl, circle_vs, circle_fs);
context.locations['stroke'] = {
'a_pos': gl.getAttribLocation(context.programs['stroke'], 'a_pos'),
@ -106,6 +147,17 @@ function init_webgl(state, context) { @@ -106,6 +147,17 @@ function init_webgl(state, context) {
'u_translation': gl.getUniformLocation(context.programs['quad'], 'u_translation'),
'u_layer': gl.getUniformLocation(context.programs['quad'], 'u_layer'),
'u_outline': gl.getUniformLocation(context.programs['quad'], 'u_outline'),
'u_texture': gl.getUniformLocation(context.programs['quad'], 'u_texture'),
};
context.locations['circle'] = {
'a_pos': gl.getAttribLocation(context.programs['circle'], 'a_pos'),
'a_texcoord': gl.getAttribLocation(context.programs['circle'], 'a_texcoord'),
'a_color': gl.getAttribLocation(context.programs['circle'], 'a_color'),
'u_res': gl.getUniformLocation(context.programs['circle'], 'u_res'),
'u_scale': gl.getUniformLocation(context.programs['circle'], 'u_scale'),
'u_translation': gl.getUniformLocation(context.programs['circle'], 'u_translation'),
'u_layer': gl.getUniformLocation(context.programs['circle'], 'u_layer'),
};
context.buffers['stroke'] = {
@ -118,6 +170,12 @@ function init_webgl(state, context) { @@ -118,6 +170,12 @@ function init_webgl(state, context) {
'b_texcoord': context.gl.createBuffer(),
};
context.buffers['circle'] = {
'b_pos': context.gl.createBuffer(),
'b_texcoord': context.gl.createBuffer(),
'b_color': context.gl.createBuffer(),
};
const resize_canvas = (entries) => {
// https://www.khronos.org/webgl/wiki/HandlingHighDPI
const entry = entries[0];

Loading…
Cancel
Save