|
|
|
function push_point_xy(s, x, y) {
|
|
|
|
ser_f32(s, x);
|
|
|
|
ser_f32(s, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
function push_point_xyzrgb(s, x, y, z, r, g, b) {
|
|
|
|
ser_f32(s, x);
|
|
|
|
ser_f32(s, y);
|
|
|
|
ser_f32(s, z);
|
|
|
|
ser_u8(s, r);
|
|
|
|
ser_u8(s, g);
|
|
|
|
ser_u8(s, b);
|
|
|
|
ser_align(s, 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
function push_quad_xyzrgb(s, p1x, p1y, p4x, p4y, z, r, g, b) {
|
|
|
|
push_point_xyzrgb(s, p1x, p1y, z, r, g, b);
|
|
|
|
push_point_xyzrgb(s, p4x, p1y, z, r, g, b);
|
|
|
|
push_point_xyzrgb(s, p1x, p4y, z, r, g, b);
|
|
|
|
|
|
|
|
push_point_xyzrgb(s, p4x, p4y, z, r, g, b);
|
|
|
|
push_point_xyzrgb(s, p1x, p4y, z, r, g, b);
|
|
|
|
push_point_xyzrgb(s, p4x, p1y, z, r, g, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
const MAX_POINTS_PER_QUAD = 10;
|
|
|
|
const MAX_QUAD_SIDE = 256;
|
|
|
|
|
|
|
|
function count_stroke_quads(points) {
|
|
|
|
let min_x, min_y, max_x, max_y;
|
|
|
|
let points_per_quad = 0;
|
|
|
|
|
|
|
|
// TODO
|
|
|
|
|
|
|
|
return Math.ceil(points.length / MAX_POINTS_PER_QUAD);
|
|
|
|
}
|
|
|
|
|
|
|
|
function push_stroke(context, stroke) {
|
|
|
|
// if (stroke.stroke_id !== 1123776468) {
|
|
|
|
// return;
|
|
|
|
// }
|
|
|
|
|
|
|
|
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 === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let points_from = context.point_serializer.offset / (4 * 2); // 4 is sizeof(f32) btw, just sain'
|
|
|
|
const points_start = points_from;
|
|
|
|
|
|
|
|
let min_x, min_y, max_x, max_y;
|
|
|
|
|
|
|
|
min_x = Math.floor(points[0].x - stroke_width / 2);
|
|
|
|
max_x = Math.ceil(points[0].x + stroke_width / 2);
|
|
|
|
|
|
|
|
min_y = Math.floor(points[0].y - stroke_width / 2);
|
|
|
|
max_y = Math.ceil(points[0].y + stroke_width / 2);
|
|
|
|
|
|
|
|
let points_per_quad = 0;
|
|
|
|
|
|
|
|
for (let i = 0; i < points.length; ++i) {
|
|
|
|
const p = points[i];
|
|
|
|
|
|
|
|
min_x = Math.min(min_x, Math.floor(p.x - stroke_width / 2));
|
|
|
|
min_y = Math.min(min_y, Math.floor(p.y - stroke_width / 2));
|
|
|
|
max_x = Math.max(max_x, Math.ceil(p.x + stroke_width / 2));
|
|
|
|
max_y = Math.max(max_y, Math.ceil(p.y + stroke_width / 2));
|
|
|
|
|
|
|
|
push_point_xy(context.point_serializer, p.x, p.y);
|
|
|
|
|
|
|
|
points_per_quad++;
|
|
|
|
|
|
|
|
if (points_per_quad == MAX_POINTS_PER_QUAD) {
|
|
|
|
let points_to = points_from + MAX_POINTS_PER_QUAD;
|
|
|
|
|
|
|
|
if (points_from > points_start) {
|
|
|
|
// 1 point overlap to prevent gaps
|
|
|
|
ser_u32(context.index_serializer, points_from - 1);
|
|
|
|
} else {
|
|
|
|
ser_u32(context.index_serializer, points_from);
|
|
|
|
}
|
|
|
|
|
|
|
|
ser_u32(context.index_serializer, points_to);
|
|
|
|
|
|
|
|
push_quad_xyzrgb(context.quad_serializer, min_x, min_y, max_x, max_y, stroke_width / 2, r, g, b);
|
|
|
|
|
|
|
|
min_x = Math.floor(p.x - stroke_width / 2);
|
|
|
|
max_x = Math.ceil(p.x + stroke_width / 2);
|
|
|
|
|
|
|
|
min_y = Math.floor(p.y - stroke_width / 2);
|
|
|
|
max_y = Math.ceil(p.y + stroke_width / 2);
|
|
|
|
|
|
|
|
points_from = points_to;
|
|
|
|
points_per_quad = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (points_per_quad > 0) {
|
|
|
|
const points_to = points_from + points_per_quad;
|
|
|
|
|
|
|
|
if (points_from > points_start) {
|
|
|
|
ser_u32(context.index_serializer, points_from - 1);
|
|
|
|
} else {
|
|
|
|
ser_u32(context.index_serializer, points_from);
|
|
|
|
}
|
|
|
|
|
|
|
|
ser_u32(context.index_serializer, points_to);
|
|
|
|
|
|
|
|
push_quad_xyzrgb(context.quad_serializer, min_x, min_y, max_x, max_y, stroke_width / 2, r, g, b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function geometry_prepare_stroke(state) {
|
|
|
|
if (!state.online) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
'color': state.players[state.me].color,
|
|
|
|
'width': state.players[state.me].width,
|
|
|
|
'points': process_stroke(state, state.players[state.me].points),
|
|
|
|
'user_id': state.me,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function geometry_add_stroke(state, context, stroke) {
|
|
|
|
if (!state.online || !stroke) return;
|
|
|
|
|
|
|
|
const stroke_quads = count_stroke_quads(stroke.points);
|
|
|
|
// const stroke_quads = Math.ceil(stroke.points.length / MAX_POINTS_PER_QUAD);
|
|
|
|
|
|
|
|
// Points
|
|
|
|
let point_bytes_left = context.point_serializer.size - context.point_serializer.offset;
|
|
|
|
let point_bytes_needed = stroke.points.length * config.bytes_per_point;
|
|
|
|
|
|
|
|
if (point_bytes_needed % 8192 != 0) {
|
|
|
|
point_bytes_needed += 8192 - point_bytes_needed % 8192;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (point_bytes_left < point_bytes_needed) {
|
|
|
|
const extend_points_by = Math.ceil((context.point_serializer.size + point_bytes_needed) * 1.62);
|
|
|
|
context.point_serializer = ser_extend(context.point_serializer, extend_points_by);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Indices
|
|
|
|
const index_bytes_left = context.index_serializer.size - context.index_serializer.offset;
|
|
|
|
const index_bytes_needed = stroke_quads * (4 * 2);
|
|
|
|
|
|
|
|
if (index_bytes_left < index_bytes_needed) {
|
|
|
|
const extend_indices_by = Math.ceil((context.index_serializer.size + index_bytes_needed) * 1.62);
|
|
|
|
context.index_serializer = ser_extend(context.index_serializer, extend_indices_by);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Quads
|
|
|
|
const quad_bytes_left = context.quad_serializer.size - context.quad_serializer.offset;
|
|
|
|
const quad_bytes_needed = stroke_quads * 6 * (4 * 4);
|
|
|
|
|
|
|
|
if (quad_bytes_left < quad_bytes_needed) {
|
|
|
|
const extend_quads_by = Math.ceil((context.quad_serializer.size + quad_bytes_needed) * 1.62);
|
|
|
|
context.quad_serializer = ser_extend(context.quad_serializer, extend_quads_by);
|
|
|
|
}
|
|
|
|
|
|
|
|
push_stroke(context, stroke);
|
|
|
|
}
|
|
|
|
|
|
|
|
function geometry_delete_stroke(state, context, stroke_index) {
|
|
|
|
// NEXT: deleted wrong stroke
|
|
|
|
let offset = 0;
|
|
|
|
|
|
|
|
for (let i = 0; i < stroke_index; ++i) {
|
|
|
|
const event = state.events[i];
|
|
|
|
|
|
|
|
if (event.type === EVENT.STROKE) {
|
|
|
|
offset += (event.points.length * 12 + 6) * config.bytes_per_point;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const stroke = state.events[stroke_index];
|
|
|
|
|
|
|
|
for (let i = 0; i < stroke.points.length * 12 + 6; ++i) {
|
|
|
|
context.static_stroke_serializer.view.setUint8(offset + config.bytes_per_point - 1, 125);
|
|
|
|
offset += config.bytes_per_point;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function recompute_dynamic_data(state, context) {
|
|
|
|
// let bytes_needed = 0;
|
|
|
|
|
|
|
|
// for (const player_id in state.players) {
|
|
|
|
// const player = state.players[player_id];
|
|
|
|
// if (player.points.length > 0) {
|
|
|
|
// bytes_needed += player.points.length * config.bytes_per_point;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// if (bytes_needed > context.dynamic_stroke_serializer.size) {
|
|
|
|
// context.dynamic_stroke_serializer = serializer_create(Math.ceil(bytes_needed * 1.62));
|
|
|
|
// } else {
|
|
|
|
// context.dynamic_stroke_serializer.offset = 0;
|
|
|
|
// }
|
|
|
|
|
|
|
|
// for (const player_id in state.players) {
|
|
|
|
// // player has the same data as their current stroke: points, color, width
|
|
|
|
// const player = state.players[player_id];
|
|
|
|
// if (player.points.length > 0) {
|
|
|
|
// push_stroke(context.dynamic_stroke_serializer, player);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
|
|
|
|
function geometry_add_point(state, context, player_id, point) {
|
|
|
|
if (!state.online) return;
|
|
|
|
state.players[player_id].points.push(point);
|
|
|
|
recompute_dynamic_data(state, context);
|
|
|
|
}
|
|
|
|
|
|
|
|
function geometry_clear_player(state, context, player_id) {
|
|
|
|
if (!state.online) return;
|
|
|
|
state.players[player_id].points.length = 0;
|
|
|
|
recompute_dynamic_data(state, context);
|
|
|
|
}
|
|
|
|
|
|
|
|
function add_image(context, image_id, bitmap, p) {
|
|
|
|
return; // TODO
|
|
|
|
|
|
|
|
const x = p.x;
|
|
|
|
const y = p.y;
|
|
|
|
const gl = context.gl;
|
|
|
|
const id = Object.keys(context.textures['image']).length;
|
|
|
|
|
|
|
|
context.textures['image'][id] = {
|
|
|
|
'texture': gl.createTexture(),
|
|
|
|
'image_id': image_id
|
|
|
|
};
|
|
|
|
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, context.textures['image'][id].texture);
|
|
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bitmap);
|
|
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
|
|
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);
|
|
|
|
|
|
|
|
context.quad_positions.push(...[
|
|
|
|
x, y,
|
|
|
|
x, y + bitmap.height,
|
|
|
|
x + bitmap.width, y + bitmap.height,
|
|
|
|
|
|
|
|
x + bitmap.width, y,
|
|
|
|
x, y,
|
|
|
|
x + bitmap.width, y + bitmap.height,
|
|
|
|
]);
|
|
|
|
|
|
|
|
context.quad_texcoords.push(...[
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
function move_image(context, image_event) {
|
|
|
|
const x = image_event.x;
|
|
|
|
const y = image_event.y;
|
|
|
|
|
|
|
|
const count = Object.keys(context.textures['image']).length;
|
|
|
|
|
|
|
|
for (let id = 0; id < count; ++id) {
|
|
|
|
const image = context.textures['image'][id];
|
|
|
|
if (image.image_id === image_event.image_id) {
|
|
|
|
context.quad_positions[id * 12 + 0] = x;
|
|
|
|
context.quad_positions[id * 12 + 1] = y;
|
|
|
|
context.quad_positions[id * 12 + 2] = x;
|
|
|
|
context.quad_positions[id * 12 + 3] = y + image_event.height;
|
|
|
|
context.quad_positions[id * 12 + 4] = x + image_event.width;
|
|
|
|
context.quad_positions[id * 12 + 5] = y + image_event.height;
|
|
|
|
|
|
|
|
context.quad_positions[id * 12 + 6] = x + image_event.width;
|
|
|
|
context.quad_positions[id * 12 + 7] = y;
|
|
|
|
context.quad_positions[id * 12 + 8] = x;
|
|
|
|
context.quad_positions[id * 12 + 9] = y;
|
|
|
|
context.quad_positions[id * 12 + 10] = x + image_event.width;
|
|
|
|
context.quad_positions[id * 12 + 11] = y + image_event.height;
|
|
|
|
|
|
|
|
context.quad_positions_f32 = new Float32Array(context.quad_positions);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function image_at(state, x, y) {
|
|
|
|
for (let i = state.events.length - 1; i >= 0; --i) {
|
|
|
|
const event = state.events[i];
|
|
|
|
if (event.type === EVENT.IMAGE && !event.deleted) {
|
|
|
|
if ('height' in event && 'width' in event) {
|
|
|
|
if (event.x <= x && x <= event.x + event.width && event.y <= y && y <= event.y + event.height) {
|
|
|
|
return event;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|