use wrflib::*;
#[repr(C)]
#[derive(Debug, Clone)]
pub struct DrawLines3dInstance {
pub position_before: Vec3,
pub position_start: Vec3,
pub position_end: Vec3,
pub position_after: Vec3,
pub color_start: Vec4,
pub color_end: Vec4,
pub scale: f32,
}
impl DrawLines3dInstance {
pub fn from_segment(start: Vec3, end: Vec3, color: Vec4, scale: f32) -> Self {
Self {
position_before: start,
position_start: start,
position_end: end,
position_after: end,
color_start: color,
color_end: color,
scale,
}
}
}
#[repr(C)]
struct DrawLines3dUniforms {
vertex_transform: Mat4,
}
pub struct DrawLines3dOptions {
pub vertex_transform: Mat4,
}
impl Default for DrawLines3dOptions {
fn default() -> Self {
Self { vertex_transform: Mat4::identity() }
}
}
pub struct DrawLines3d {}
impl DrawLines3d {
pub fn draw(cx: &mut Cx, data: &[DrawLines3dInstance], options: DrawLines3dOptions) {
let area = cx.add_instances(&SHADER, data);
area.write_user_uniforms(cx, DrawLines3dUniforms { vertex_transform: options.vertex_transform });
}
}
static SHADER: Shader = Shader {
build_geom: Some(QuadIns::build_geom),
code_to_concatenate: &[
Cx::STD_SHADER,
code_fragment!(
r#"
geometry geom: vec2;
uniform vertex_transform: mat4;
instance in_pos_before: vec3;
instance in_pos_start: vec3;
instance in_pos_end: vec3;
instance in_pos_after: vec3;
instance in_color_start: vec4;
instance in_color_end: vec4;
instance in_scale: float;
varying color: vec4;
fn project(pos: vec3) -> vec4 {
return camera_projection * camera_view * vertex_transform * vec4(pos, 1.0);
}
// Transforms a vertex to clip space, accounting for aspect ratio
fn clip(v: vec4) -> vec4 {
let w = draw_clip.z - draw_clip.x;
let h = draw_clip.w - draw_clip.y;
let aspect = w / h;
return v / v.w * aspect;
}
fn rotate_ccw(v: vec2) -> vec2 {
return vec2(-v.y, v.x);
}
fn vertex() -> vec4 {
let is_top = geom.y == 1.;
let is_left = geom.x == 0.;
let pos = is_left ? in_pos_start : in_pos_end;
color = is_left ? in_color_start : in_color_end;
// The line thickness should be scaled the same way the camera scales other distances.
// projection[0].xyz is the result of projecting a unit x-vector, so its length represents
// how much distances are scaled by the camera projection.
let thickness = 0.5 * in_scale * length(camera_projection[0].xyz);
thickness = is_top ? thickness : -thickness;
let projected_pos = project(pos);
// Transform all points to clip space so calculations are done in 2D and
// the resulting normal are already facing the camera
let clip_before = clip(project(in_pos_before)).xy;
let clip_start = clip(project(in_pos_start)).xy;
let clip_end = clip(project(in_pos_end)).xy;
let clip_after = clip(project(in_pos_after)).xy;
// Vector comparision (i.e, `clip_before == clip_start`) is not allowed?
// TODO(hernan): fix vector comparision
let is_first = length(clip_before - clip_start) < 0.001;
let is_last = length(clip_end - clip_after) < 0.001;
let is_endpoint = is_left ? is_first : is_last;
let dir_left = normalize(clip_start - clip_before);
let dir_current = normalize(clip_end - clip_start);
let dir_right = normalize(clip_after - clip_end);
let normal_current = rotate_ccw(dir_current);
let normal_left = normalize(rotate_ccw(dir_left));
let normal_right = normalize(rotate_ccw(dir_right));
if is_endpoint == true {
projected_pos += vec4(normal_current * thickness, 0, 0);
return projected_pos;
}
let cos_start = clamp(-dot(dir_left, dir_current), -1., 1.);
let cos_end = clamp(-dot(dir_current, dir_right), -1., 1.);
let too_sharp_start = cos_start > 0.01;
let too_sharp_end = cos_end > 0.01;
let too_sharp = is_left ? too_sharp_start : too_sharp_end;
let normal: vec2;
if too_sharp == true {
// Fold join: The resulting offset is a vector perpendicular to the bisector
// between the direction of the previous (or next) line segment and the current
// one. Folding swap top/bottom vertices, so we need to acount for the direction
// of the folding
let turning_right_start = dot(dir_left, rotate_ccw(dir_current)) > 0.;
let turning_right_end = dot(dir_current, rotate_ccw(dir_right)) > 0.;
let turning_right = is_left ? turning_right_start : turning_right_end;
let perp = is_left ? normal_left : normal_current;
let dir = is_left ? dir_left : dir_current;
let scale_perp = is_left ? -1. : 1.;
let scale_dir = (turning_right == is_left) ? 1. : -1.;
let tan_half_start = sqrt((1. - cos_start) / (1. + cos_start));
let tan_half_end = sqrt((1. - cos_end) / (1. + cos_end));
let tan_half = is_left ? tan_half_start : tan_half_end;
normal = scale_perp * perp + scale_dir * dir * tan_half;
} else {
// Miter join: compute the corner normal as the vector pointing
// halfway between the previous (or next) segment and the current one
let bisector_start = rotate_ccw(normalize(dir_left + dir_current)); // angle bisector of ABC
let bisector_end = rotate_ccw(normalize(dir_current + dir_right)); // angle bisector of BCD
let bisector = is_left ? bisector_start : bisector_end;
let sin_half_start = sqrt((1. - cos_start) / 2.);
let sin_half_end = sqrt((1. - cos_end) / 2.);
let sin_half = is_left ? sin_half_start : sin_half_end;
normal = bisector / sin_half;
}
projected_pos += vec4(normal * thickness, 0, 0);
return projected_pos;
}
fn pixel() -> vec4 {
return color;
}"#
),
],
..Shader::DEFAULT
};