contrast_renderer 0.1.1

A web-gpu based 2D render engine
Documentation
use crate::{
    curve::{
        integral_cubic_uniform_tangent_angle, integral_quadratic_uniform_tangent_angle, rational_cubic_control_points_to_power_basis,
        rational_cubic_first_order_derivative, rational_cubic_point, rational_cubic_uniform_tangent_angle,
        rational_quadratic_control_points_to_power_basis, rational_quadratic_first_order_derivative, rational_quadratic_point,
        rational_quadratic_uniform_tangent_angle,
    },
    error::{Error, ERROR_MARGIN},
    path::{CurveApproximation, Path, SegmentType, StrokeOptions},
    safe_float::SafeFloat,
    utils::{line_line_intersection, point_to_vec, rotate_90_degree_clockwise, vec_to_point, weighted_vec_to_point},
    vertex::{Vertex2f1i, Vertex3f1i},
};
use geometric_algebra::{ppga2d, Dual, GeometricProduct, InnerProduct, Magnitude, OuterProduct, RegressiveProduct, Signum, SquaredMagnitude, Zero};

fn offset_control_point(control_point: ppga2d::Point, tangent: ppga2d::Plane, offset: f32) -> ppga2d::Point {
    let mut direction = tangent.dual();
    direction.g0[0] = 0.0;
    direction *= ppga2d::Scalar { g0: offset };
    control_point + direction
}

fn emit_stroke_vertex(path_line_vertices: &mut Vec<Vertex2f1i>, path_index: usize, offset_along_path: f32, vertex: ppga2d::Point, side: f32) {
    path_line_vertices.push(Vertex2f1i(point_to_vec(vertex), [side, offset_along_path], path_index as u32));
}

fn emit_stroke_vertices(
    builder: &mut StrokeBuilder,
    stroke_options: &StrokeOptions,
    path_index: usize,
    length_accumulator: f32,
    point: ppga2d::Point,
    tangent: ppga2d::Plane,
) {
    let offset_along_path = length_accumulator / stroke_options.width.unwrap();
    emit_stroke_vertex(
        &mut builder.path_line_vertices,
        path_index,
        offset_along_path,
        offset_control_point(point, tangent, (stroke_options.offset.unwrap() - 0.5) * stroke_options.width.unwrap()),
        -0.5,
    );
    emit_stroke_vertex(
        &mut builder.path_line_vertices,
        path_index,
        offset_along_path,
        offset_control_point(point, tangent, (stroke_options.offset.unwrap() + 0.5) * stroke_options.width.unwrap()),
        0.5,
    );
}

fn emit_stroke_join(
    builder: &mut StrokeBuilder,
    proto_hull: &mut Vec<SafeFloat<f32, 2>>,
    stroke_options: &StrokeOptions,
    length_accumulator: &mut f32,
    control_point: ppga2d::Point,
    previous_tangent: ppga2d::Plane,
    next_tangent: ppga2d::Plane,
) {
    let tangets_dot_product = previous_tangent.inner_product(next_tangent).g0;
    if (tangets_dot_product - 1.0).abs() <= ERROR_MARGIN {
        return;
    }
    let side_sign = previous_tangent.outer_product(next_tangent).g0[0].signum();
    let miter_clip = stroke_options.width.unwrap() * stroke_options.miter_clip.unwrap();
    let side_offset = (stroke_options.offset.unwrap() - side_sign * 0.5) * stroke_options.width.unwrap();
    let previous_edge_vertex = offset_control_point(control_point, previous_tangent, side_offset);
    let next_edge_vertex = offset_control_point(control_point, next_tangent, side_offset);
    let previous_edge_tangent: ppga2d::Plane = previous_tangent
        .inner_product(previous_edge_vertex)
        .geometric_product(previous_edge_vertex)
        .into();
    let next_edge_tangent: ppga2d::Plane = next_tangent.inner_product(next_edge_vertex).geometric_product(next_edge_vertex).into();
    let intersection = line_line_intersection(previous_edge_tangent, next_edge_tangent);
    let mut vertices = [control_point, previous_edge_vertex, next_edge_vertex, intersection, intersection];
    let anti_parallel = (tangets_dot_product + 1.0).abs() <= ERROR_MARGIN;
    if anti_parallel || control_point.regressive_product(intersection).magnitude().g0 > miter_clip {
        let mid_tangent = if anti_parallel {
            -rotate_90_degree_clockwise(previous_tangent)
        } else {
            (previous_tangent + next_tangent).signum()
        };
        let clipping_vertex = offset_control_point(control_point, mid_tangent, -side_sign * miter_clip);
        let clipping_plane: ppga2d::Plane = mid_tangent.inner_product(clipping_vertex).geometric_product(clipping_vertex).into();
        vertices[3] = line_line_intersection(previous_edge_tangent, clipping_plane);
        vertices[4] = line_line_intersection(clipping_plane, next_edge_tangent);
        proto_hull.push(point_to_vec(vertices[3]).into());
        proto_hull.push(point_to_vec(vertices[4]).into());
    } else {
        proto_hull.push(point_to_vec(vertices[3]).into());
    }
    let scaled_tangent = previous_tangent
        / ppga2d::Scalar {
            g0: -stroke_options.width.unwrap(),
        };
    let start_index = builder.joint_vertices.len();
    let offset_along_path = *length_accumulator / stroke_options.width.unwrap();
    for vertex in vertices.iter() {
        builder.joint_vertices.push(Vertex3f1i(
            point_to_vec(*vertex),
            [
                side_sign * vertex.regressive_product(scaled_tangent).g0,
                vertex.regressive_product(control_point).inner_product(scaled_tangent).g0,
                offset_along_path,
            ],
            stroke_options.dynamic_stroke_options_group as u32,
        ));
    }
    let mut indices: Vec<u16> = (start_index as u16..(builder.joint_vertices.len() + 1) as u16).collect();
    *indices.iter_mut().last().unwrap() = (-1isize) as u16;
    builder.joint_indices.append(&mut indices);
    *length_accumulator += tangets_dot_product.acos() / (std::f32::consts::PI * 2.0) * stroke_options.width.unwrap();
    cut_stroke_polygon(builder, proto_hull);
    emit_stroke_vertices(
        builder,
        stroke_options,
        stroke_options.dynamic_stroke_options_group,
        *length_accumulator,
        control_point,
        next_tangent,
    );
}

fn cut_stroke_polygon(builder: &mut StrokeBuilder, proto_hull: &mut Vec<SafeFloat<f32, 2>>) {
    if !builder.path_line_vertices.is_empty() {
        proto_hull.append(&mut builder.path_line_vertices.iter().map(|vertex| vertex.0.into()).collect());
        let start_index = builder.line_vertices.len();
        builder.line_vertices.append(&mut builder.path_line_vertices);
        let mut indices: Vec<u16> = (start_index as u16..(builder.line_vertices.len() + 1) as u16).collect();
        *indices.iter_mut().last().unwrap() = (-1isize) as u16;
        builder.line_indices.append(&mut indices);
    }
}

macro_rules! emit_curve_stroke {
    ($builder:expr, $stroke_options:expr, $length_accumulator:expr,
     $previous_control_point:expr, $power_basis:expr, $angle_step:ident, $uniform_tangent_angle:expr,
     $point:ident, $tangent:ident $(,)?) => {
        let parameters: Vec<f32> = match $stroke_options.curve_approximation {
            CurveApproximation::UniformlySpacedParameters(steps) => (1..steps + 1).map(|i| i as f32 / steps as f32).collect(),
            CurveApproximation::UniformTangentAngle($angle_step) => $uniform_tangent_angle,
        };
        let mut previous_point = $previous_control_point;
        for mut t in parameters {
            let mut tangent = $tangent(&$power_basis, t);
            if tangent.squared_magnitude().g0 == 0.0 {
                if t < 0.5 {
                    t += f32::EPSILON;
                } else {
                    t -= f32::EPSILON;
                }
                tangent = $tangent(&$power_basis, t);
            }
            tangent = tangent.signum();
            let mut point = $point(&$power_basis, t);
            point = point / ppga2d::Scalar { g0: point.g0[0] };
            $length_accumulator += previous_point.regressive_product(point).magnitude().g0;
            emit_stroke_vertices(
                $builder,
                &$stroke_options,
                $stroke_options.dynamic_stroke_options_group,
                $length_accumulator,
                point,
                tangent,
            );
            previous_point = point;
        }
    };
}

#[derive(Default)]
pub struct StrokeBuilder {
    pub line_indices: Vec<u16>,
    pub joint_indices: Vec<u16>,
    pub line_vertices: Vec<Vertex2f1i>,
    pub joint_vertices: Vec<Vertex3f1i>,
    path_line_vertices: Vec<Vertex2f1i>,
}

fn get_quadratic_tangents(control_points: &[ppga2d::Point; 3]) -> (ppga2d::Plane, ppga2d::Plane) {
    let mut segment_start_tangent = control_points[0].regressive_product(control_points[1]).signum();
    let mut segment_end_tangent = control_points[1].regressive_product(control_points[2]).signum();
    if segment_start_tangent.g0[0].is_nan() || segment_end_tangent.g0[0].is_nan() {
        segment_start_tangent = control_points[0].regressive_product(control_points[2]).signum();
        segment_end_tangent = segment_start_tangent;
    }
    (segment_start_tangent, segment_end_tangent)
}

fn get_cubic_tangents(control_points: &[ppga2d::Point; 4]) -> (ppga2d::Plane, ppga2d::Plane) {
    let mut segment_start_tangent = control_points[0].regressive_product(control_points[1]).signum();
    if segment_start_tangent.g0[0].is_nan() {
        segment_start_tangent = control_points[0].regressive_product(control_points[2]).signum();
    }
    let mut segment_end_tangent = control_points[2].regressive_product(control_points[3]).signum();
    if segment_end_tangent.g0[0].is_nan() {
        segment_end_tangent = control_points[1].regressive_product(control_points[3]).signum();
    }
    if segment_start_tangent.g0[0].is_nan() || segment_end_tangent.g0[0].is_nan() {
        segment_end_tangent = control_points[0].regressive_product(control_points[3]).signum();
    }
    (segment_start_tangent, segment_end_tangent)
}

impl StrokeBuilder {
    pub fn add_path(&mut self, proto_hull: &mut Vec<SafeFloat<f32, 2>>, path: &Path) -> Result<(), Error> {
        let stroke_options = path.stroke_options.as_ref().unwrap();
        let mut previous_control_point = vec_to_point(path.start.unwrap());
        let mut first_tangent = ppga2d::Plane::zero();
        let mut previous_tangent = ppga2d::Plane::zero();
        let mut line_segment_iter = path.line_segments.iter();
        let mut integral_quadratic_curve_segment_iter = path.integral_quadratic_curve_segments.iter().peekable();
        let mut integral_cubic_curve_segment_iter = path.integral_cubic_curve_segments.iter().peekable();
        let mut rational_quadratic_curve_segment_iter = path.rational_quadratic_curve_segments.iter().peekable();
        let mut rational_cubic_curve_segment_iter = path.rational_cubic_curve_segments.iter().peekable();
        let mut length_accumulator = 0.0;
        let mut is_first_segment = true;
        for segment_type in path.segment_types.iter() {
            let next_control_point;
            let segment_start_tangent;
            let segment_end_tangent;
            match segment_type {
                SegmentType::Line => {
                    let segment = line_segment_iter.next().unwrap();
                    next_control_point = vec_to_point(segment.control_points[0].unwrap());
                    segment_start_tangent = previous_control_point.regressive_product(next_control_point).signum();
                    segment_end_tangent = segment_start_tangent;
                }
                SegmentType::IntegralQuadraticCurve => {
                    let segment = integral_quadratic_curve_segment_iter.peek().unwrap();
                    next_control_point = vec_to_point(segment.control_points[1].unwrap());
                    (segment_start_tangent, segment_end_tangent) = get_quadratic_tangents(&[
                        previous_control_point,
                        vec_to_point(segment.control_points[0].unwrap()),
                        next_control_point,
                    ]);
                }
                SegmentType::IntegralCubicCurve => {
                    let segment = integral_cubic_curve_segment_iter.peek().unwrap();
                    next_control_point = vec_to_point(segment.control_points[2].unwrap());
                    (segment_start_tangent, segment_end_tangent) = get_cubic_tangents(&[
                        previous_control_point,
                        vec_to_point(segment.control_points[0].unwrap()),
                        vec_to_point(segment.control_points[1].unwrap()),
                        next_control_point,
                    ]);
                }
                SegmentType::RationalQuadraticCurve => {
                    let segment = rational_quadratic_curve_segment_iter.peek().unwrap();
                    next_control_point = vec_to_point(segment.control_points[1].unwrap());
                    (segment_start_tangent, segment_end_tangent) = get_quadratic_tangents(&[
                        previous_control_point,
                        vec_to_point(segment.control_points[0].unwrap()),
                        next_control_point,
                    ]);
                }
                SegmentType::RationalCubicCurve => {
                    let segment = rational_cubic_curve_segment_iter.peek().unwrap();
                    next_control_point = vec_to_point(segment.control_points[2].unwrap());
                    (segment_start_tangent, segment_end_tangent) = get_cubic_tangents(&[
                        previous_control_point,
                        vec_to_point(segment.control_points[0].unwrap()),
                        vec_to_point(segment.control_points[1].unwrap()),
                        next_control_point,
                    ]);
                }
            }
            if segment_start_tangent.g0[0].is_nan() || segment_end_tangent.g0[0].is_nan() {
                continue;
            }
            if is_first_segment {
                is_first_segment = false;
                first_tangent = segment_start_tangent;
                if !stroke_options.closed {
                    let normal = rotate_90_degree_clockwise(segment_start_tangent);
                    emit_stroke_vertices(
                        self,
                        stroke_options,
                        stroke_options.dynamic_stroke_options_group,
                        length_accumulator - 0.5 * stroke_options.width.unwrap(),
                        offset_control_point(previous_control_point, normal, 0.5 * stroke_options.width.unwrap().abs()),
                        segment_start_tangent,
                    );
                }
                if stroke_options.closed || *segment_type != SegmentType::Line {
                    emit_stroke_vertices(
                        self,
                        stroke_options,
                        stroke_options.dynamic_stroke_options_group,
                        length_accumulator,
                        previous_control_point,
                        segment_start_tangent,
                    );
                }
            } else {
                emit_stroke_join(
                    self,
                    proto_hull,
                    stroke_options,
                    &mut length_accumulator,
                    previous_control_point,
                    previous_tangent,
                    segment_start_tangent,
                );
            }
            match segment_type {
                SegmentType::Line => {
                    length_accumulator += previous_control_point.regressive_product(next_control_point).magnitude().g0;
                    emit_stroke_vertices(
                        self,
                        stroke_options,
                        stroke_options.dynamic_stroke_options_group,
                        length_accumulator,
                        next_control_point,
                        segment_end_tangent,
                    );
                }
                SegmentType::IntegralQuadraticCurve => {
                    let segment = integral_quadratic_curve_segment_iter.next().unwrap();
                    let power_basis = rational_quadratic_control_points_to_power_basis(&[
                        previous_control_point,
                        vec_to_point(segment.control_points[0].unwrap()),
                        vec_to_point(segment.control_points[1].unwrap()),
                    ]);
                    emit_curve_stroke!(
                        self,
                        stroke_options,
                        length_accumulator,
                        previous_control_point,
                        power_basis,
                        angle_step,
                        integral_quadratic_uniform_tangent_angle(&power_basis, segment_start_tangent, segment_end_tangent, angle_step.unwrap()),
                        rational_quadratic_point,
                        rational_quadratic_first_order_derivative,
                    );
                }
                SegmentType::IntegralCubicCurve => {
                    let segment = integral_cubic_curve_segment_iter.next().unwrap();
                    let power_basis = rational_cubic_control_points_to_power_basis(&[
                        previous_control_point,
                        vec_to_point(segment.control_points[0].unwrap()),
                        vec_to_point(segment.control_points[1].unwrap()),
                        vec_to_point(segment.control_points[2].unwrap()),
                    ]);
                    emit_curve_stroke!(
                        self,
                        stroke_options,
                        length_accumulator,
                        previous_control_point,
                        power_basis,
                        angle_step,
                        integral_cubic_uniform_tangent_angle(&power_basis, angle_step.unwrap()),
                        rational_cubic_point,
                        rational_cubic_first_order_derivative,
                    );
                }
                SegmentType::RationalQuadraticCurve => {
                    let segment = rational_quadratic_curve_segment_iter.next().unwrap();
                    let power_basis = rational_quadratic_control_points_to_power_basis(&[
                        previous_control_point,
                        weighted_vec_to_point(segment.weight.unwrap(), segment.control_points[0].unwrap()),
                        vec_to_point(segment.control_points[1].unwrap()),
                    ]);
                    emit_curve_stroke!(
                        self,
                        stroke_options,
                        length_accumulator,
                        previous_control_point,
                        power_basis,
                        angle_step,
                        rational_quadratic_uniform_tangent_angle(&power_basis, segment_start_tangent, segment_end_tangent, angle_step.unwrap()),
                        rational_quadratic_point,
                        rational_quadratic_first_order_derivative,
                    );
                }
                SegmentType::RationalCubicCurve => {
                    let segment = rational_cubic_curve_segment_iter.next().unwrap();
                    let weights = segment.weights.unwrap();
                    let power_basis = rational_cubic_control_points_to_power_basis(&[
                        weighted_vec_to_point(weights[0], point_to_vec(previous_control_point)),
                        weighted_vec_to_point(weights[1], segment.control_points[0].unwrap()),
                        weighted_vec_to_point(weights[2], segment.control_points[1].unwrap()),
                        weighted_vec_to_point(weights[3], segment.control_points[2].unwrap()),
                    ]);
                    emit_curve_stroke!(
                        self,
                        stroke_options,
                        length_accumulator,
                        previous_control_point,
                        power_basis,
                        angle_step,
                        rational_cubic_uniform_tangent_angle(&power_basis, angle_step.unwrap()),
                        rational_cubic_point,
                        rational_cubic_first_order_derivative,
                    );
                }
            }
            previous_control_point = next_control_point;
            previous_tangent = segment_end_tangent;
        }
        if stroke_options.closed {
            let line_segment = previous_control_point.regressive_product(vec_to_point(path.start.unwrap()));
            let length = line_segment.magnitude();
            if length.g0 > 0.0 {
                let segment_tangent = line_segment / length;
                emit_stroke_join(
                    self,
                    proto_hull,
                    stroke_options,
                    &mut length_accumulator,
                    previous_control_point,
                    previous_tangent,
                    segment_tangent,
                );
                length_accumulator += length.g0;
                emit_stroke_vertices(
                    self,
                    stroke_options,
                    stroke_options.dynamic_stroke_options_group,
                    length_accumulator,
                    vec_to_point(path.start.unwrap()),
                    segment_tangent,
                );
                emit_stroke_join(
                    self,
                    proto_hull,
                    stroke_options,
                    &mut length_accumulator,
                    vec_to_point(path.start.unwrap()),
                    segment_tangent,
                    first_tangent,
                );
            } else {
                emit_stroke_join(
                    self,
                    proto_hull,
                    stroke_options,
                    &mut length_accumulator,
                    vec_to_point(path.start.unwrap()),
                    previous_tangent,
                    first_tangent,
                );
            }
        } else {
            cut_stroke_polygon(self, proto_hull);
            emit_stroke_vertices(
                self,
                stroke_options,
                stroke_options.dynamic_stroke_options_group | 0x10000,
                length_accumulator,
                previous_control_point,
                previous_tangent,
            );
            let normal = rotate_90_degree_clockwise(previous_tangent);
            emit_stroke_vertices(
                self,
                stroke_options,
                stroke_options.dynamic_stroke_options_group | 0x10000,
                length_accumulator + 0.5 * stroke_options.width.unwrap(),
                offset_control_point(previous_control_point, normal, -0.5 * stroke_options.width.unwrap().abs()),
                previous_tangent,
            );
        }
        cut_stroke_polygon(self, proto_hull);
        Ok(())
    }
}