You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
8.0 KiB
263 lines
8.0 KiB
function push_point(s, x, y, ax, ay, bx, by, thickness, r, g, b, stroke_id) { |
|
ser_f32(s, x); |
|
ser_f32(s, y); |
|
ser_f32(s, thickness); |
|
ser_f32(s, ax); |
|
ser_f32(s, ay); |
|
ser_f32(s, bx); |
|
ser_f32(s, by); |
|
ser_u8(s, r); |
|
ser_u8(s, g); |
|
ser_u8(s, b); |
|
ser_align(s, 4); |
|
ser_u32(s, stroke_id); |
|
} |
|
|
|
function push_quad(s, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y, ax, ay, bx, by, thickness, r, g, b, stroke_id) { |
|
push_point(s, p1x, p1y, ax, ay, bx, by, thickness, r, g, b, stroke_id); |
|
push_point(s, p2x, p2y, ax, ay, bx, by, thickness, r, g, b, stroke_id); |
|
push_point(s, p3x, p3y, ax, ay, bx, by, thickness, r, g, b, stroke_id); |
|
push_point(s, p4x, p4y, ax, ay, bx, by, thickness, r, g, b, stroke_id); |
|
} |
|
|
|
function push_stroke(s, stroke, stroke_index) { |
|
// if (stroke.stroke_id !== 1123776468) { |
|
// return; |
|
// } |
|
|
|
const stroke_width = stroke.width; |
|
const points = stroke.points; |
|
const color_u32 = stroke.color; |
|
const radius = stroke_width / 2; |
|
|
|
if (points.length < 2) { |
|
return; |
|
} |
|
|
|
const r = (color_u32 >> 16) & 0xFF; |
|
const g = (color_u32 >> 8) & 0xFF; |
|
const b = color_u32 & 0xFF; |
|
|
|
for (let i = 0; i < points.length - 1; ++i) { |
|
const from = points[i]; |
|
const to = points[i + 1]; |
|
|
|
const dir_x = to.x - from.x; |
|
const dir_y = to.y - from.y; |
|
const len = Math.sqrt(dir_x * dir_x + dir_y * dir_y); |
|
|
|
const dir1_x = dir_x / len; |
|
const dir1_y = dir_y / len; |
|
|
|
const up_x = dir_y / len; |
|
const up_y = -dir_x / len; |
|
|
|
let p1_x = from.x + (up_x - dir1_x) * radius; |
|
let p1_y = from.y + (up_y - dir1_y) * radius; |
|
|
|
let p2_x = to.x + (up_x + dir1_x) * radius; |
|
let p2_y = to.y + (up_y + dir1_y) * radius; |
|
|
|
let p3_x = from.x + (-up_x - dir1_x) * radius; |
|
let p3_y = from.y + (-up_y - dir1_y) * radius; |
|
|
|
let p4_x = to.x + (-up_x + dir1_x) * radius; |
|
let p4_y = to.y + (-up_y + dir1_y) * radius; |
|
|
|
push_quad(s, |
|
p1_x, p1_y, |
|
p2_x, p2_y, |
|
p3_x, p3_y, |
|
p4_x, p4_y, |
|
from.x, from.y, |
|
to.x, to.y, |
|
stroke_width, |
|
r, g, b, |
|
stroke_index |
|
); |
|
} |
|
} |
|
|
|
function geometry_prepare_stroke(state) { |
|
if (!state.online) { |
|
return null; |
|
} |
|
|
|
if (state.players[state.me].points.length === 0) { |
|
return null; |
|
} |
|
|
|
const points = process_stroke(state, state.players[state.me].points); |
|
|
|
return { |
|
'color': state.players[state.me].color, |
|
'width': state.players[state.me].width, |
|
'points': points, |
|
'user_id': state.me, |
|
}; |
|
} |
|
|
|
function geometry_add_stroke(state, context, stroke, stroke_index) { |
|
if (!state.online || !stroke) return; |
|
|
|
stroke.bbox = stroke_bbox(stroke.points); |
|
|
|
let bytes_left = context.static_serializer.size - context.static_serializer.offset; |
|
let bytes_needed = stroke.points.length * 4 * config.bytes_per_point; |
|
|
|
if (bytes_left < bytes_needed) { |
|
const extend_to = Math.ceil((context.static_serializer.size + bytes_needed) * 1.62 / 4) * 4; |
|
context.static_serializer = ser_extend(context.static_serializer, extend_to); |
|
context.need_static_allocate = true; |
|
} |
|
|
|
push_stroke(context.static_serializer, stroke, stroke_index); |
|
context.need_static_upload = true; |
|
} |
|
|
|
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 * 6 * config.bytes_per_point; |
|
} |
|
} |
|
|
|
if (bytes_needed > context.dynamic_serializer.size) { |
|
context.dynamic_serializer = serializer_create(Math.ceil(bytes_needed * 1.62)); |
|
} else { |
|
context.dynamic_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_serializer, player, 0); // TODO: stroke index ?? |
|
} |
|
} |
|
|
|
context.need_dynamic_upload = true; |
|
} |
|
|
|
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); |
|
schedule_draw(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; |
|
}
|
|
|