diff --git a/client/tools.js b/client/tools.js
index d0ce92d..55b7eb7 100644
--- a/client/tools.js
+++ b/client/tools.js
@@ -1,23 +1,26 @@
-function tools_switch(tool) {
- if (storage.tools.active_element) {
- storage.tools.active_element.classList.remove('active');
+function tools_switch(state, tool) {
+ if (state.tools.active_element) {
+ state.tools.active_element.classList.remove('active');
}
- storage.tools.active = tool;
- storage.tools.active_element = document.querySelector(`.tool[data-tool="${tool}"]`);
- storage.tools.active_element.classList.add('active');
+ state.tools.active = tool;
+ state.tools.active_element = document.querySelector(`.tool[data-tool="${tool}"]`);
+ state.tools.active_element.classList.add('active');
}
-function tools_init() {
+function init_tools(state, context) {
const pencil = document.querySelector('.tool[data-tool="pencil"]');
const ruler = document.querySelector('.tool[data-tool="ruler"]');
const eraser = document.querySelector('.tool[data-tool="eraser"]');
const undo = document.querySelector('.tool[data-tool="undo"]');
- pencil.addEventListener('click', () => tools_switch('pencil'));
- ruler.addEventListener('click', () => tools_switch('ruler'));
- eraser.addEventListener('click', () => tools_switch('eraser'));
- undo.addEventListener('click', queue_undo);
+ pencil.addEventListener('click', () => tools_switch(state, 'pencil'));
+ ruler.addEventListener('click', () => tools_switch(state, 'ruler'));
+ eraser.addEventListener('click', () => tools_switch(state, 'eraser'));
+ undo.addEventListener('click', () => {
+ pop_stroke(state, context);
+ window.requestAnimationFrame(() => draw(state, context));
+ });
- tools_switch('pencil');
+ tools_switch(state, 'pencil');
}
\ No newline at end of file
diff --git a/client/webgl.html b/client/webgl.html
index 8bc937c..5db4379 100644
--- a/client/webgl.html
+++ b/client/webgl.html
@@ -8,6 +8,7 @@
+
diff --git a/client/webgl.js b/client/webgl.js
index b8d9ed3..47247c5 100644
--- a/client/webgl.js
+++ b/client/webgl.js
@@ -2,23 +2,54 @@ document.addEventListener('DOMContentLoaded', main);
function draw(state, context) {
const gl = context.gl;
- const locations = context.locations;
- const buffers = context.buffers;
const width = window.innerWidth;
const height = window.innerHeight;
+
+ let locations;
+ let buffers;
gl.viewport(0, 0, context.canvas.width, context.canvas.height);
gl.clearColor(context.bgcolor.r, context.bgcolor.g, context.bgcolor.b, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
- gl.useProgram(context.program);
+
+ // Draw images
+ locations = context.locations['quad'];
+ buffers = context.buffers['quad'];
+
+ gl.useProgram(context.programs['quad']);
gl.enableVertexAttribArray(locations['a_pos']);
- gl.enableVertexAttribArray(locations['a_color']);
+ gl.enableVertexAttribArray(locations['a_texcoord']);
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'], 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);
+ gl.bufferData(gl.ARRAY_BUFFER, context.quad_positions_f32, gl.STATIC_DRAW);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_texcoord']);
+ gl.vertexAttribPointer(locations['a_texcoord'], 2, gl.FLOAT, false, 0, 0);
+ gl.bufferData(gl.ARRAY_BUFFER, context.quad_texcoords_f32, gl.STATIC_DRAW);
+
+ gl.drawArrays(gl.TRIANGLES, 0, context.quad_positions.length / 2);
+
+ // Draw strokes
+ locations = context.locations['stroke'];
+ buffers = context.buffers['stroke'];
+
+ gl.useProgram(context.programs['stroke']);
+
+ gl.enableVertexAttribArray(locations['a_pos']);
+ 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_pos_size = context.static_positions_f32.byteLength + context.dynamic_positions_f32.byteLength;
const total_color_size = context.static_colors_u8.byteLength + context.dynamic_colors_u8.byteLength;
@@ -82,31 +113,43 @@ function main() {
'current_stroke': {
'color': 0,
+ 'width': 8,
'points': [],
},
'strokes': [],
+
+ 'tools': {
+ 'active': null,
+ 'active_element': null,
+ },
};
const context = {
'canvas': null,
'gl': null,
- 'program': null,
+ 'programs': {},
'buffers': {},
'locations': {},
+ 'textures': {},
'static_positions': [],
'dynamic_positions': [],
+ 'quad_positions': [],
+ 'quad_texcoords': [],
'static_colors': [],
'dynamic_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),
+ 'quad_positions_f32': new Float32Array(0),
+ 'quad_texcoords_f32': new Float32Array(0),
'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0},
};
init_webgl(state, context);
init_listeners(state, context);
+ init_tools(state, context);
window.requestAnimationFrame(() => draw(state, context));
}
\ No newline at end of file
diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js
index 9e23005..2567ca3 100644
--- a/client/webgl_geometry.js
+++ b/client/webgl_geometry.js
@@ -16,7 +16,8 @@ function push_circle_at(positions, cl, r, g, b, c, o) {
}
function push_stroke(state, stroke, positions, colors) {
- const stroke_width = state.stroke_width;
+ const starting_length = positions.length;
+ const stroke_width = stroke.width;
const points = stroke.points;
const color_u32 = stroke.color;
@@ -26,6 +27,7 @@ function push_stroke(state, stroke, positions, colors) {
if (points.length < 2) {
// TODO
+ stroke.popcount = 0;
return;
}
@@ -88,6 +90,20 @@ function push_stroke(state, stroke, positions, colors) {
// TODO: angle
push_circle_at(positions, colors, r, g, b, points[points.length - 1], circle_offsets);
+
+ stroke.popcount = positions.length - starting_length;
+}
+
+function pop_stroke(state, context) {
+ if (state.strokes.length > 0) {
+ const popped = state.strokes.pop();
+
+ context.static_positions.length -= popped.popcount;
+ context.static_colors.length -= popped.popcount / 2 * 3;
+
+ context.static_positions_f32 = new Float32Array(context.static_positions);
+ context.static_colors_u8 = new Uint8Array(context.static_colors);
+ }
}
function add_static_stroke(state, context, stroke) {
diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js
index cc9640c..e63763e 100644
--- a/client/webgl_listeners.js
+++ b/client/webgl_listeners.js
@@ -90,6 +90,7 @@ function mouseup(e, state, context) {
if (state.drawing) {
const stroke = {
'color': Math.round(Math.random() * 4294967295),
+ 'width': 8, //Math.round((Math.random() * 20) + 4),
'points': process_stroke(state.current_stroke.points)
};
@@ -248,6 +249,11 @@ function touchmove(e, state, context) {
const old_finger_midpoint = mid_v2(state.touch.first_finger_position, state.touch.second_finger_position);
const new_finger_midpoint = mid_v2(first_finger_position, second_finger_position);
+ const new_finger_midpoint_canvas = mid_v2(
+ screen_to_canvas(state, first_finger_position),
+ screen_to_canvas(state, second_finger_position)
+ );
+
const old_finger_distance = dist_v2(state.touch.first_finger_position, state.touch.second_finger_position);
const new_finger_distance = dist_v2(first_finger_position, second_finger_position);
@@ -264,8 +270,8 @@ function touchmove(e, state, context) {
const scale_by = new_finger_distance / old_finger_distance;
const dz = state.canvas.zoom * (scale_by - 1.0);
- const zoom_offset_x = dz * new_finger_midpoint.x;
- const zoom_offset_y = dz * new_finger_midpoint.y;
+ const zoom_offset_x = dz * new_finger_midpoint_canvas.x;
+ const zoom_offset_y = dz * new_finger_midpoint_canvas.y;
if (config.min_zoom <= state.canvas.zoom * scale_by && state.canvas.zoom * scale_by <= config.max_zoom) {
state.canvas.zoom *= scale_by;
@@ -291,6 +297,7 @@ function touchend(e, state, context) {
const stroke = {
'color': Math.round(Math.random() * 4294967295),
+ 'width': 8, // Math.round((Math.random() * 20) + 4),
'points': process_stroke(state.current_stroke.points)
};
diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js
index 09acc85..41deb6c 100644
--- a/client/webgl_shaders.js
+++ b/client/webgl_shaders.js
@@ -1,4 +1,4 @@
-const vertex_shader_source = `
+const stroke_vs_src = `
attribute vec2 a_pos;
attribute vec3 a_color;
@@ -19,7 +19,7 @@ const vertex_shader_source = `
}
`;
-const fragment_shader_source = `
+const stroke_fs_src = `
precision mediump float;
varying vec3 v_color;
@@ -29,6 +29,39 @@ const fragment_shader_source = `
}
`;
+const tquad_vs_src = `
+ attribute vec2 a_pos;
+ attribute vec2 a_texcoord;
+
+ uniform vec2 u_scale;
+ uniform vec2 u_res;
+ uniform vec2 u_translation;
+ uniform int u_layer;
+
+ varying vec2 v_texcoord;
+
+ 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;
+ gl_Position = vec4(screen11, u_layer, 1);
+ }
+`;
+
+const tquad_fs_src = `
+ precision mediump float;
+
+ varying vec2 v_texcoord;
+
+ uniform sampler2D u_texture;
+
+ void main() {
+ gl_FragColor = texture2D(u_texture, v_texcoord);
+ }
+`;
+
function init_webgl(state, context) {
context.canvas = document.querySelector('#c');
context.gl = context.canvas.getContext('webgl', {
@@ -37,25 +70,88 @@ function init_webgl(state, context) {
'antialias': true,
});
- context.gl.enable(context.gl.BLEND);
- context.gl.blendFunc(context.gl.ONE, context.gl.ONE_MINUS_SRC_ALPHA);
-
- const vertex_shader = create_shader(context.gl, context.gl.VERTEX_SHADER, vertex_shader_source);
- const fragment_shader = create_shader(context.gl, context.gl.FRAGMENT_SHADER, fragment_shader_source);
- const program = create_program(context.gl, vertex_shader, fragment_shader)
-
- context.program = program;
-
- context.locations['a_pos'] = context.gl.getAttribLocation(program, 'a_pos');
- context.locations['a_color'] = context.gl.getAttribLocation(program, 'a_color');
-
- context.locations['u_res'] = context.gl.getUniformLocation(program, 'u_res');
- context.locations['u_scale'] = context.gl.getUniformLocation(program, 'u_scale');
- context.locations['u_translation'] = context.gl.getUniformLocation(program, 'u_translation');
- context.locations['u_layer'] = context.gl.getUniformLocation(program, 'u_layer');
-
- context.buffers['b_pos'] = context.gl.createBuffer();
- context.buffers['b_color'] = context.gl.createBuffer();
+ const gl = context.gl;
+
+ gl.enable(gl.BLEND);
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+
+ const stroke_vs = create_shader(gl, gl.VERTEX_SHADER, stroke_vs_src);
+ const stroke_fs = create_shader(gl, gl.FRAGMENT_SHADER, stroke_fs_src);
+
+ const quad_vs = create_shader(gl, gl.VERTEX_SHADER, tquad_vs_src);
+ const quad_fs = create_shader(gl, gl.FRAGMENT_SHADER, tquad_fs_src);
+
+ context.programs['stroke'] = create_program(gl, stroke_vs, stroke_fs);
+ context.programs['quad'] = create_program(gl, quad_vs, quad_fs);
+
+ context.locations['stroke'] = {
+ 'a_pos': gl.getAttribLocation(context.programs['stroke'], 'a_pos'),
+ 'a_color': gl.getAttribLocation(context.programs['stroke'], 'a_color'),
+ 'u_res': gl.getUniformLocation(context.programs['stroke'], 'u_res'),
+ 'u_scale': gl.getUniformLocation(context.programs['stroke'], 'u_scale'),
+ 'u_translation': gl.getUniformLocation(context.programs['stroke'], 'u_translation'),
+ 'u_layer': gl.getUniformLocation(context.programs['stroke'], 'u_layer'),
+ };
+
+ context.locations['quad'] = {
+ 'a_pos': gl.getAttribLocation(context.programs['quad'], 'a_pos'),
+ 'a_texcoord': gl.getAttribLocation(context.programs['quad'], 'a_texcoord'),
+ 'u_res': gl.getUniformLocation(context.programs['quad'], 'u_res'),
+ 'u_scale': gl.getUniformLocation(context.programs['quad'], 'u_scale'),
+ 'u_translation': gl.getUniformLocation(context.programs['quad'], 'u_translation'),
+ 'u_layer': gl.getUniformLocation(context.programs['quad'], 'u_layer'),
+ };
+
+ context.buffers['stroke'] = {
+ 'b_pos': context.gl.createBuffer(),
+ 'b_color': context.gl.createBuffer(),
+ };
+
+ context.buffers['quad'] = {
+ 'b_pos': context.gl.createBuffer(),
+ 'b_texcoord': context.gl.createBuffer(),
+ };
+
+ context.textures['test'] = gl.createTexture();
+
+ // Fill the texture with a 1x1 blue pixel.
+ gl.bindTexture(gl.TEXTURE_2D, context.textures['test']);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
+ new Uint8Array([0, 0, 255, 255]));
+
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+
+ var image = new Image();
+ image.src = "http://192.168.100.2/images/3697505915";
+ image.addEventListener('load', function() {
+ // Now that the image has loaded make copy it to the texture.
+ context.quad_positions = [
+ 100, 100,
+ 100, 100 + image.height,
+ 100 + image.width, 100 + image.height,
+
+ 100 + image.width, 100,
+ 100, 100,
+ 100 + image.width, 100 + image.height,
+ ];
+
+ context.quad_texcoords = [
+ 0, 0,
+ 0, 1,
+ 1, 1,
+ 1, 0,
+ 0, 0,
+ 1, 1,
+ ];
+
+ context.quad_positions_f32 = new Float32Array(context.quad_positions);
+ context.quad_texcoords_f32 = new Float32Array(context.quad_texcoords);
+
+ gl.bindTexture(gl.TEXTURE_2D, context.textures['test']);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,gl.UNSIGNED_BYTE, image);
+ });
const resize_canvas = (entries) => {
// https://www.khronos.org/webgl/wiki/HandlingHighDPI