use web_sys::WebGl2RenderingContext;
mod shader;
use shader::*;
pub use shader::Buffer;
const SQUARE_FRAG_SHADER_STR: &str = r#"#version 300 es
precision mediump float;
out vec4 out_color;
uniform vec4 bg;
void main() {
//coord is between -0.5 and 0.5
vec2 coord = gl_PointCoord - vec2(0.5,0.5);
out_color = bg;
}
"#;
const CIRCLE_FRAG_SHADER_STR: &str = r#"#version 300 es
precision mediump float;
out vec4 out_color;
uniform vec4 bg;
void main() {
//coord is between -0.5 and 0.5
vec2 coord = gl_PointCoord - vec2(0.5,0.5);
float dissqr=dot(coord,coord);
if(dissqr > 0.25){
discard;
}
out_color = bg;
}
"#;
const VERT_SHADER_STR: &str = r#"#version 300 es
in vec2 position;
uniform vec2 offset;
uniform mat3 mmatrix;
uniform float point_size;
void main() {
gl_PointSize = point_size;
vec3 pp=vec3(position.xy+offset,1.0);
gl_Position = vec4((mmatrix*pp).xy,0.0, 1.0);
}
"#;
pub struct StaticBuffer(Buffer);
impl std::ops::Deref for StaticBuffer {
type Target = Buffer;
fn deref(&self) -> &Buffer {
&self.0
}
}
pub type Vertex = [f32; 2];
impl StaticBuffer {
pub fn new(ctx: &WebGl2RenderingContext, verts: &[Vertex]) -> Result<Self, String> {
let mut buffer = StaticBuffer(Buffer::new(ctx)?);
buffer.0.num_verts = verts.len();
ctx.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&buffer.0.buffer));
let n_bytes = verts.len() * std::mem::size_of::<Vertex>();
let points_buf: &[u8] =
unsafe { std::slice::from_raw_parts(verts.as_ptr() as *const u8, n_bytes) };
ctx.buffer_data_with_u8_array(
WebGl2RenderingContext::ARRAY_BUFFER,
points_buf,
WebGl2RenderingContext::STATIC_DRAW,
);
Ok(buffer)
}
}
pub struct DynamicBuffer(Buffer);
impl std::ops::Deref for DynamicBuffer {
type Target = Buffer;
fn deref(&self) -> &Buffer {
&self.0
}
}
impl DynamicBuffer {
pub fn new(ctx: &WebGl2RenderingContext) -> Result<Self, String> {
Ok(DynamicBuffer(Buffer::new(ctx)?))
}
pub fn update_clear(&mut self, verts: &mut Vec<Vertex>) {
self.update_no_clear(verts);
verts.clear();
}
pub fn update_no_clear(&mut self, vertices: &[Vertex]) {
let ctx = &self.0.ctx;
self.0.num_verts = vertices.len();
ctx.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&self.0.buffer));
let n_bytes = vertices.len() * std::mem::size_of::<Vertex>();
let points_buf: &[u8] =
unsafe { std::slice::from_raw_parts(vertices.as_ptr() as *const u8, n_bytes) };
ctx.buffer_data_with_u8_array(
WebGl2RenderingContext::ARRAY_BUFFER,
points_buf,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
}
struct Args<'a> {
pub verts: &'a Buffer,
pub primitive: u32,
pub game_dim: [f32; 2],
pub as_square: bool,
pub color: &'a [f32; 4],
pub offset: [f32; 2],
pub point_size: f32,
}
use wasm_bindgen::prelude::*;
pub fn ctx_wrap(a: &WebGl2RenderingContext) -> CtxWrap {
CtxWrap::new(a)
}
pub struct CtxWrap {
pub ctx: WebGl2RenderingContext,
}
impl std::ops::Deref for CtxWrap {
type Target = WebGl2RenderingContext;
fn deref(&self) -> &Self::Target {
&self.ctx
}
}
impl CtxWrap {
pub fn new(a: &WebGl2RenderingContext) -> Self {
CtxWrap { ctx: a.clone() }
}
pub fn setup_alpha(&self) {
self.disable(WebGl2RenderingContext::DEPTH_TEST);
self.enable(WebGl2RenderingContext::BLEND);
self.blend_func(
WebGl2RenderingContext::SRC_ALPHA,
WebGl2RenderingContext::ONE_MINUS_SRC_ALPHA,
);
}
pub fn buffer_dynamic(&self) -> DynamicBuffer {
DynamicBuffer::new(self).unwrap_throw()
}
pub fn buffer_static_clear(&self, a: &mut Vec<Vertex>) -> StaticBuffer {
let b = self.buffer_static_no_clear(a);
a.clear();
b
}
pub fn buffer_static_no_clear(&self, a: &[Vertex]) -> StaticBuffer {
StaticBuffer::new(self, a).unwrap_throw()
}
pub fn shader_system(&self) -> ShaderSystem {
ShaderSystem::new(self).unwrap_throw()
}
pub fn draw_clear(&self, color: [f32; 4]) {
let [a, b, c, d] = color;
self.ctx.clear_color(a, b, c, d);
self.ctx
.clear(web_sys::WebGl2RenderingContext::COLOR_BUFFER_BIT);
}
}
pub struct Rect {
pub x: f32,
pub y: f32,
pub w: f32,
pub h: f32,
}
impl From<axgeom::Rect<f32>> for Rect {
fn from(a: axgeom::Rect<f32>) -> Rect {
Rect {
x: a.x.start,
y: a.y.start,
w: a.x.end - a.x.start,
h: a.y.end - a.y.start,
}
}
}
pub struct ShaderSystem {
circle_program: GlProgram,
square_program: GlProgram,
ctx: WebGl2RenderingContext,
}
impl Drop for ShaderSystem {
fn drop(&mut self) {
self.ctx.delete_program(Some(&self.circle_program.program));
self.ctx.delete_program(Some(&self.square_program.program));
}
}
impl ShaderSystem {
pub fn new(ctx: &WebGl2RenderingContext) -> Result<ShaderSystem, String> {
let circle_program = GlProgram::new(ctx, VERT_SHADER_STR, CIRCLE_FRAG_SHADER_STR)?;
let square_program = GlProgram::new(ctx, VERT_SHADER_STR, SQUARE_FRAG_SHADER_STR)?;
Ok(ShaderSystem {
circle_program,
square_program,
ctx: ctx.clone(),
})
}
fn draw(&mut self, args: Args) {
let Args {
verts,
primitive,
game_dim,
as_square,
color,
offset,
point_size,
} = args;
assert_eq!(verts.ctx, self.ctx);
let scalex = 2.0 / game_dim[0];
let scaley = 2.0 / game_dim[1];
let tx = -1.0;
let ty = 1.0;
let matrix = [scalex, 0.0, 0.0, 0.0, -scaley, 0.0, tx, ty, 1.0];
if as_square {
self.square_program
.draw(verts, primitive, offset, &matrix, point_size, color);
} else {
self.circle_program
.draw(verts, primitive, offset, &matrix, point_size, color);
};
}
pub fn view(&mut self, game_dim: impl Into<[f32; 2]>, offset: impl Into<[f32; 2]>) -> View {
View {
sys: self,
offset: offset.into(),
dim: game_dim.into(),
}
}
}
pub struct View<'a> {
sys: &'a mut ShaderSystem,
offset: [f32; 2],
dim: [f32; 2],
}
impl View<'_> {
pub fn draw_squares(&mut self, verts: &Buffer, point_size: f32, color: &[f32; 4]) {
self.sys.draw(Args {
verts,
primitive: WebGl2RenderingContext::POINTS,
game_dim: self.dim,
as_square: true,
color,
offset: self.offset,
point_size,
})
}
pub fn draw_triangles(&mut self, verts: &Buffer, color: &[f32; 4]) {
self.sys.draw(Args {
verts,
primitive: WebGl2RenderingContext::TRIANGLES,
game_dim: self.dim,
as_square: true,
color,
offset: self.offset,
point_size: 0.0,
})
}
pub fn draw_circles(&mut self, verts: &Buffer, point_size: f32, color: &[f32; 4]) {
self.sys.draw(Args {
verts,
primitive: WebGl2RenderingContext::POINTS,
game_dim: self.dim,
as_square: false,
color,
offset: self.offset,
point_size,
})
}
}
pub fn shapes(a: &mut Vec<[f32; 2]>) -> ShapeBuilder {
ShapeBuilder::new(a)
}
pub struct ShapeBuilder<'a> {
inner: &'a mut Vec<[f32; 2]>,
}
impl<'a> std::ops::Deref for ShapeBuilder<'a> {
type Target = Vec<[f32; 2]>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<'a> ShapeBuilder<'a> {
pub fn clear(&mut self) {
self.inner.clear();
}
pub fn new(inner: &'a mut Vec<[f32; 2]>) -> Self {
ShapeBuilder { inner }
}
pub fn dot_line(
&mut self,
radius: f32,
start: impl Into<[f32; 2]>,
end: impl Into<[f32; 2]>,
) -> &mut Self {
let buffer = &mut self.inner;
use axgeom::*;
let start = Vec2::from(start.into());
let end = Vec2::from(end.into());
let offset = end - start;
let dis_sqr = offset.magnitude2();
let dis = dis_sqr.sqrt();
let norm = offset / dis;
let num = (dis / (radius)).floor() as usize;
for i in 0..num {
let pos = start + norm * (i as f32) * radius;
buffer.push(pos.into());
}
self
}
pub fn line(
&mut self,
radius: f32,
start: impl Into<[f32; 2]>,
end: impl Into<[f32; 2]>,
) -> &mut Self {
let buffer = &mut self.inner;
use axgeom::*;
let start = Vec2::from(start.into());
let end = Vec2::from(end.into());
let offset = end - start;
let k = offset.rotate_90deg_right().normalize_to(1.0);
let start1 = start + k * radius;
let start2 = start - k * radius;
let end1 = end + k * radius;
let end2 = end - k * radius;
let arr: [[f32; 2]; 6] = [
start1.into(),
start2.into(),
end1.into(),
start2.into(),
end1.into(),
end2.into(),
];
buffer.extend(arr);
self
}
pub fn rect(&mut self, rect: impl Into<Rect>) -> &mut Self {
use axgeom::vec2;
let rect: Rect = rect.into();
let buffer = &mut self.inner;
let start = vec2(rect.x, rect.y);
let dim = vec2(rect.w, rect.h);
let arr: [[f32; 2]; 6] = [
start.into(),
(start + vec2(dim.x, 0.0)).into(),
(start + vec2(0.0, dim.y)).into(),
(start + vec2(dim.x, 0.0)).into(),
(start + dim).into(),
(start + vec2(0.0, dim.y)).into(),
];
buffer.extend(arr);
self
}
}
pub fn convert_coord(canvas: &web_sys::HtmlElement, e: &web_sys::MouseEvent) -> [f32; 2] {
let rect = canvas.get_bounding_client_rect();
let canvas_width: f64 = canvas
.get_attribute("width")
.unwrap_throw()
.parse()
.unwrap_throw();
let canvas_height: f64 = canvas
.get_attribute("height")
.unwrap_throw()
.parse()
.unwrap_throw();
let scalex = canvas_width / rect.width();
let scaley = canvas_height / rect.height();
let [x, y] = [e.client_x() as f64, e.client_y() as f64];
let [x, y] = [(x - rect.left()) * scalex, (y - rect.top()) * scaley];
[x as f32, y as f32]
}