|  |  |  | @ -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,16 +140,32 @@ function total_dynamic_positions(context) {@@ -150,16 +140,32 @@ 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; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  |  | @ -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); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
	
		
			
				
					|  |  |  | 
 |