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) { cl.push(r, g, b); } } function push_stroke(state, stroke, positions, colors) { const starting_length = positions.length; const stroke_width = stroke.width; const points = stroke.points; const color_u32 = stroke.color; const r = (color_u32 >> 16) & 0xFF; const g = (color_u32 >> 8) & 0xFF; const b = color_u32 & 0xFF; if (points.length < 2) { // TODO stroke.popcount = 0; return; } // Simple 12 point circle (store offsets and reuse) 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; const nextpx = points[i + 1].x; const nextpy = points[i + 1].y; const d1x = nextpx - px; const d1y = nextpy - py; // Perpendicular to (d1x, d1y), points to the LEFT let perp1x = -d1y; let perp1y = d1x; const perpnorm1 = Math.sqrt(perp1x * perp1x + perp1y * perp1y); perp1x /= perpnorm1; perp1y /= perpnorm1; const s1x = px + perp1x * stroke_width / 2; const s1y = py + perp1y * stroke_width / 2; const s2x = px - perp1x * stroke_width / 2; const s2y = py - perp1y * stroke_width / 2; const s3x = nextpx + perp1x * stroke_width / 2; const s3y = nextpy + perp1y * stroke_width / 2; const s4x = nextpx - perp1x * stroke_width / 2; const s4y = nextpy - perp1y * stroke_width / 2; positions.push(s1x, s1y, s2x, s2y, s4x, s4y); positions.push(s1x, s1y, s4x, s4y, s3x, s3y); for (let j = 0; j < 6; ++j) { colors.push(r, g, b); } // Rotate circle offsets so that the diameter of the circle is // perpendicular to the (dx, dy) vector. This way the circle won't // "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); } // 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) { state.strokes.push(stroke); push_stroke(state, stroke, context.static_positions, context.static_colors); context.static_positions_f32 = new Float32Array(context.static_positions); context.static_colors_u8 = new Uint8Array(context.static_colors); } function update_dynamic_stroke(state, context, point) { state.current_stroke.points.push(point); context.dynamic_positions.length = 0; // TODO: incremental context.dynamic_colors.length = 0; push_stroke(state, state.current_stroke, context.dynamic_positions, context.dynamic_colors); context.dynamic_positions_f32 = new Float32Array(context.dynamic_positions); context.dynamic_colors_u8 = new Uint8Array(context.dynamic_colors); } function clear_dynamic_stroke(state, context) { state.current_stroke.points.length = 0; context.dynamic_positions.length = 0; context.dynamic_colors.length = 0; context.dynamic_positions_f32 = new Float32Array(0); context.dynamic_colors_u8 = new Uint8Array(0); }