diff options
author | Sébastien Dailly <sebastien@chimrod.com> | 2020-12-16 14:39:42 +0100 |
---|---|---|
committer | Sébastien Dailly <sebastien@chimrod.com> | 2020-12-16 14:39:42 +0100 |
commit | 4f262d6540281487f79870aff589ca92f5d2f6c6 (patch) | |
tree | 940e59d943715366d1aa72bb93f248dcd65ab992 |
Initial commit
-rwxr-xr-x | .gitignore | 3 | ||||
-rwxr-xr-x | curves/bezier.ml | 197 | ||||
-rwxr-xr-x | curves/bezier.mli | 40 | ||||
-rwxr-xr-x | curves/bspline.ml | 149 | ||||
-rwxr-xr-x | curves/bspline.mli | 24 | ||||
-rwxr-xr-x | curves/dd_splines.pdf | bin | 0 -> 184248 bytes | |||
-rwxr-xr-x | curves/dune | 7 | ||||
-rwxr-xr-x | draw/draw.ml | 233 | ||||
-rwxr-xr-x | draw/dune | 8 | ||||
-rwxr-xr-x | draw/point.ml | 78 | ||||
-rwxr-xr-x | draw/point.mli | 13 | ||||
-rwxr-xr-x | dune | 25 | ||||
-rwxr-xr-x | dune-project | 1 | ||||
-rwxr-xr-x | events/dune | 7 | ||||
-rwxr-xr-x | events/timer.ml | 24 | ||||
-rwxr-xr-x | events/timer.mli | 7 | ||||
-rwxr-xr-x | index.html | 14 | ||||
-rwxr-xr-x | matrix/EltsI.ml | 28 | ||||
-rwxr-xr-x | matrix/Helpers.ml | 16 | ||||
-rwxr-xr-x | matrix/Matrix.ml | 529 | ||||
-rwxr-xr-x | matrix/MatrixI.ml | 105 | ||||
-rwxr-xr-x | matrix/Order.ml | 2 | ||||
-rwxr-xr-x | matrix/dune | 3 | ||||
-rwxr-xr-x | script.html | 48 | ||||
-rwxr-xr-x | script.ml | 275 | ||||
-rwxr-xr-x | theme/dune | 7 | ||||
-rwxr-xr-x | theme/nord.ml | 39 | ||||
-rwxr-xr-x | theme/theme.ml | 1 | ||||
-rwxr-xr-x | tools/dune | 6 | ||||
-rwxr-xr-x | tools/utils.ml | 63 | ||||
-rwxr-xr-x | tools/utils.mli | 21 | ||||
-rwxr-xr-x | worker/dune | 9 | ||||
-rwxr-xr-x | worker/worker.ml | 5 |
33 files changed, 1987 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..d1a4873 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +_build/ +.merlin +*.swp diff --git a/curves/bezier.ml b/curves/bezier.ml new file mode 100755 index 0000000..3dedc70 --- /dev/null +++ b/curves/bezier.ml @@ -0,0 +1,197 @@ +(** + + Bezier curve +*) + +module Utils = Tools.Utils + +type quadratic = + { p0:Gg.v2 (* The starting point *) + ; p1:Gg.v2 (* The end point *) + ; ctrl:Gg.v2 } (* The control point *) + + +type t = + { p0:Gg.v2 (* The starting point *) + ; p1:Gg.v2 (* The end point *) + ; ctrl0:Gg.v2 (* The control point *) + ; ctrl1:Gg.v2 } (* The control point *) + + +(** + Build a control point for a quadratic curve for passing throuht 3 points. + taken from https://xuhehuan.com/2608.html + + + also look to https://pomax.github.io/bezierinfo/#pointcurves +*) +let three_points_quadratic + : Gg.v2 -> Gg.v2 -> Gg.v2 -> quadratic + = fun p0 c1 p1 -> + + let open Gg.V2 in + + let vect_1 = p0 - c1 + and vect_2 = p1 - c1 in + let norm1 = norm vect_1 + and norm2 = norm vect_2 in + let v = (Float.sqrt (norm1 *. norm2)) /. 2. in + + let ctrl = c1 - v * (( vect_1 / norm1) + (vect_2 / norm2)) in + {p0; p1; ctrl} + +(** + + Convert a cubic bezier curve into a quadratic one + +*) +let quadratic_to_cubic + : quadratic -> t + = fun {p0; p1; ctrl} -> + + let coef = 2. /. 3. in + + let open Gg.V2 in + { p0 + ; p1 + ; ctrl0 = mix p0 ctrl coef + ; ctrl1 = mix p1 ctrl coef } + + + +let abc_ratio + : int -> float -> float + = fun n t -> + let n' = Float.of_int n in + let bottom = (Float.pow t n') +. (Float.pow (1. -. t) n') in + let top = bottom -. 1. in + Float.abs (top /. bottom) + +let half_cubic_ratio = abc_ratio 3 0.5 + +exception Not_found + +(** + + https://pomax.github.io/bezierinfo/#pointcurves + +*) +let three_points_cubic + : float -> Gg.v2 -> Gg.v2 -> Gg.v2 -> t + = fun f p0 p1 p2 -> + + let open Gg.V2 in + + let c = half ( p0 + p2) in + let a = p1 + ((p1 - c) / half_cubic_ratio) in + + let vect1_0 = p1 - p0 in + let vect2_0 = p2 - p0 in + + let d1 = norm vect1_0 + and d2 = norm (p2 - p1) in + let t = d1 /. (d1 +. d2) in + + let angle_1_0 = angle vect1_0 + and angle_2_0 = angle vect2_0 in + + (* get our e1-e2 distances *) + let angle = mod_float + (Gg.Float.two_pi + +. angle_2_0 + -. angle_1_0) + Gg.Float.two_pi in + + let distance = (norm vect2_0) *. f in + + let bc = + if angle < 0. || angle > Gg.Float.pi then + Float.(neg distance) + else + distance in + let de1 = t *. bc + and de2 = (1. -. t) *. bc in + + (* get the circle-aligned slope as normalized dx/dy *) + let center = Utils.center p0 p1 p2 in + match center with + | None -> raise Not_found + | Some center -> + let t' = p1 - center in + let tangent0 = v + ((x p1) -. (y t')) + ((y p1) +. (x t')) + and tangent1 = v + ((x p1) +. (y t')) + ((y p1) -. (x t')) in + + let d = unit (tangent1 - tangent0) in + + (* then set up an e1 and e2 parallel to the baseline *) + let e1 = p1 + de1 * d + and e2 = p1 - de2 * d in + + (* then use those e1/e2 to derive the new hull coordinates *) + let v1 = a + (e1 - a) / (1. -. t) + and v2 = a + (e2 - a) / t in + + let ctrl0 = p0 + (v1 - p0) / t + and ctrl1 = p2 + (v2 -p2) / (1. -. t) in + + {p0; p1 = p2; ctrl0; ctrl1} + +(** Split a bezier curve in two at a given position *) +let slice + : float -> t -> t * t + = fun t {p0; p1; ctrl0; ctrl1} -> + + let mix p1 p2 = Gg.V2.mix p1 p2 t in + + let p12 = mix p0 ctrl0 + and p23 = mix ctrl0 ctrl1 + and p34 = mix ctrl1 p1 in + + let p123 = mix p12 p23 + and p234 = mix p23 p34 in + + let p1234 = mix p123 p234 in + + ( { p0 + ; ctrl0 = p12 + ; ctrl1 = p123 + ; p1 = p1234 } + , { p0 = p1234 + ; ctrl0 = p234 + ; ctrl1 = p34 + ; p1 } ) + + +let rec get_closest_point + : Gg.v2 -> t -> Gg.v2 + = fun point t -> + (* First devide the curve in two *) + let seq_0, seq_1 = slice 0.5 t in + + let p0 = t.p0 + and p1 = t.p1 + and p01 = seq_0.p1 in (* seq_0.p1 = seq_1.p0 *) + + let open Gg.V2 in + let center0 = mix p0 p01 0.5 + and center1 = mix p01 p1 0.5 in + + if Tools.Utils.equal_point 0.001 p0 p1 then + p01 + else if (norm (point - center0)) < (norm (point - center1)) then + get_closest_point point seq_0 + else + get_closest_point point seq_1 + +let reverse + : t -> t + = fun bezier -> + { + p0 = bezier.p1 + ; p1 = bezier.p0 + ; ctrl0 = bezier.ctrl1 + ; ctrl1 = bezier.ctrl0 } diff --git a/curves/bezier.mli b/curves/bezier.mli new file mode 100755 index 0000000..e90163c --- /dev/null +++ b/curves/bezier.mli @@ -0,0 +1,40 @@ +type t = + { p0:Gg.v2 (* The starting point *) + ; p1:Gg.v2 (* The end point *) + ; ctrl0:Gg.v2 (* The control point *) + ; ctrl1:Gg.v2 } (* The control point *) + +type quadratic + +(** + Build a control point for a quadratic curve for passing throuht 3 points. + taken from https://xuhehuan.com/2608.html + + + also look to https://pomax.github.io/bezierinfo/#pointcurves +*) +val three_points_quadratic + : Gg.v2 -> Gg.v2 -> Gg.v2 -> quadratic + +(** + Create a curve from three points. + + This is an implementation for + https://pomax.github.io/bezierinfo/#pointcurves + +*) +val three_points_cubic + : float -> Gg.v2 -> Gg.v2 -> Gg.v2 -> t + +val quadratic_to_cubic + : quadratic -> t + +(** Split a bezier curve in two at a given position *) +val slice + : float -> t -> t * t + +(** Return the closest point to the curve by approximation *) +val get_closest_point + : Gg.v2 -> t -> Gg.v2 + +val reverse: t -> t diff --git a/curves/bspline.ml b/curves/bspline.ml new file mode 100755 index 0000000..bb60227 --- /dev/null +++ b/curves/bspline.ml @@ -0,0 +1,149 @@ +open StdLabels + +type err = [`InvalidPath ] + +module M = Matrix.MakeMatrix (struct + + type t = Float.t + + let compare a b = + + let v = Float.compare a b in + if v = 0 then Matrix.Order.Equal + else if v > 0 then Matrix.Order.Greater + else Matrix.Order.Less + + let zero = Float.zero + let one = Float.one + + let divide = (/.) + let multiply = ( *. ) + let add = (+.) + let subtract = (-.) + exception NonElt + + + end) + +type t = Gg.v2 list + +let from_points + : Gg.v2 array -> (Gg.v2 array, [> `InvalidPath]) Result.t + = fun points -> + + let n = (Array.length points - 2) in + + if n <= 1 then + Result.error `InvalidPath + else + + (* Create the initial matrix. + + The matrix is augmented with two additionals columns, which will be + populated with the points from the path. + *) + let arr = Array.init n ~f:(fun line -> + Array.init (n +2) ~f:(fun row -> + match row - line with + | (-1) -> 1. + | 0 -> 4. + | 1 -> 1. + | _ -> 0. + ) + ) in + let matrix = M.from_array arr in + + (* Add the points from the augmented matrix *) + let points_array = points in + for line = 0 to (n -1) do + + let point = + if line = 0 then + let p0 = points_array.(0) + and p1 = points_array.(1) in + Gg.V2.(6. * p1 - p0) + else if (line + 1) = n then + let p_n_2 = points_array.(n) + and p_n_1 = points_array.(n + 1) in + Gg.V2.(6. * p_n_2 - p_n_1) + else + let n' = line + 1 in + Gg.V2.(6. * points_array.(n')) + in + let x = (Gg.V2.x point) + and y = (Gg.V2.y point) in + + + M.set_elt matrix (line + 1, n + 1) x; + M.set_elt matrix (line + 1, n + 2) y; + done; + + (* Resolve the matrix *) + let res' = M.row_reduce matrix in + + (* Extract the result as points *) + let _, col_x = M.get_column res' (n + 1) + and _, col_y = M.get_column res' (n + 2) in + + (* Build the result *) + let res = Array.make (n + 2) (Array.get points_array (n + 1) ) in + for i = 1 to n do + let point = Gg.V2.v col_x.(i - 1) col_y.(i - 1) in + Array.set res i point; + done; + Array.set res 0 (Array.get points_array 0); + Result.ok res + +let (let*) = Result.bind + +(** Build a continue curve from path + + see https://www.math.ucla.edu/~baker/149.1.02w/handouts/dd_splines.pdf +*) +let to_bezier + : ?connexion0:Gg.v2 -> ?connexion1:Gg.v2 -> t -> (Bezier.t array, [> `InvalidPath]) Result.t + = fun ?connexion0 ?connexion1 points -> + + let points' = match connexion0 with + | None -> points + | Some pt -> pt::points in + + let arr_points = match connexion1 with + | None -> Array.of_list points' + | Some pt -> + let arr = Array.make (1 + (List.length points')) pt in + List.iteri points' + ~f:(fun i value -> Array.set arr i value); + arr in + + let* bspline_points = from_points arr_points in + + let start = match connexion0 with + | None -> 1 + | Some _ -> 2 + and end_ = match connexion1 with + | None -> (Array.length bspline_points) - 1 + | Some _ -> (Array.length bspline_points) - 2 in + + let result = Array.init (end_ - start + 1) ~f:(fun i -> + + let i = i + start in + + let prev_b = Array.get bspline_points (i - 1) + and bpoint = Array.get bspline_points i + and prev_p = Array.get arr_points (i - 1) + and point = Array.get arr_points i in + let ctrl0 = Gg.V2.(mix prev_b bpoint (1. /. 3.)) + and ctrl1 = Gg.V2.(mix prev_b bpoint (2. /. 3.)) in + + let bezier = + { Bezier.p0 = prev_p + ; Bezier.p1 = point + ; Bezier.ctrl0 + ; Bezier.ctrl1 } in + + bezier + + ) in + Result.Ok result + diff --git a/curves/bspline.mli b/curves/bspline.mli new file mode 100755 index 0000000..074658d --- /dev/null +++ b/curves/bspline.mli @@ -0,0 +1,24 @@ +type t + +type err = + [ `InvalidPath (* Too few points in the path for building the curve *) + ] + +(** Convert a list of points into a beziers curves. + + At least 4 points are required for building the path. + + [to_bezier ~connexion points] create a list of beziers segments joining all + the points together. + + [connexion0] add a virtual point in the begining for helping to get the + appropriate tangent when connecting path together + + [connexion1] does the same at the end + +*) +val to_bezier + : ?connexion0:Gg.v2 + -> ?connexion1:Gg.v2 + -> Gg.v2 list + -> (Bezier.t array, [> err]) Result.t diff --git a/curves/dd_splines.pdf b/curves/dd_splines.pdf Binary files differnew file mode 100755 index 0000000..2618162 --- /dev/null +++ b/curves/dd_splines.pdf diff --git a/curves/dune b/curves/dune new file mode 100755 index 0000000..34c87fb --- /dev/null +++ b/curves/dune @@ -0,0 +1,7 @@ +(library + (name curves) + (libraries + tools + matrix + ) + ) diff --git a/draw/draw.ml b/draw/draw.ml new file mode 100755 index 0000000..12a1abc --- /dev/null +++ b/draw/draw.ml @@ -0,0 +1,233 @@ +open StdLabels +module Path = Brr_canvas.C2d.Path + +module Point = Point + +(** Translate the point in the canva area *) +let translate_point + : area:Gg.v2 -> Gg.v2 -> (float * float) + = fun ~area point -> + let x, y = Gg.V2.(to_tuple @@ mul area point) in + x, ((Gg.V2.y area) -. y) + +let translate_point' + : area:Gg.v2 -> Gg.v2 -> Gg.v2 -> (float * float) + = fun ~area vect point -> + let open Gg.V2 in + translate_point ~area + (point + vect) + + +(* Draw a straight line between two points *) +let line + : Gg.v2 -> p1:Point.t -> Path.t -> unit + = fun area ~p1 path -> + let x, y = translate_point ~area (Point.get_coord p1) in + Path.line_to path ~x ~y + +(* Draw a simple bezier curve from the three given points *) +let three_points + : Gg.v2 -> p0:Point.t -> p1:Point.t -> p2:Point.t -> Path.t -> unit + = fun area ~p0 ~p1 ~p2 path -> + let p0 = Point.get_coord p0 + and p1 = Point.get_coord p1 + and p2 = Point.get_coord p2 in + let bezier = Curves.Bezier.three_points_quadratic p0 p1 p2 + |> Curves.Bezier.quadratic_to_cubic in + let cx, cy = translate_point ~area bezier.Curves.Bezier.ctrl0 + and cx', cy' = translate_point ~area bezier.Curves.Bezier.ctrl1 + and x, y = translate_point ~area bezier.Curves.Bezier.p1 in + + Path.ccurve_to path + ~cx ~cy + ~cx' ~cy' + ~x ~y + +let multi_points + : ?connexion:Gg.v2 -> Gg.v2 -> Point.t list -> Path.t -> unit + = fun ?connexion area points path -> + + let (let*) v f = + match v with + | Ok beziers -> f beziers + | _ -> () in + + let points = List.map ~f:Point.get_coord points in + + let* beziers = Curves.Bspline.to_bezier ?connexion1:connexion points in + Array.iter beziers + ~f:(fun bezier -> + let cx, cy = translate_point ~area bezier.Curves.Bezier.ctrl0 + and cx', cy' = translate_point ~area bezier.Curves.Bezier.ctrl1 + and x, y = translate_point ~area bezier.Curves.Bezier.p1 in + + Path.ccurve_to path + ~cx ~cy + ~cx' ~cy' + ~x ~y + ) + +let circle + : Gg.v2 -> center:Gg.v2 -> float -> Path.t -> Path.t + = fun area ~center r path -> + + let cx, cy = translate_point ~area center in + Path.arc + path + ~cx ~cy + ~r + ~start:0. + ~stop:Gg.Float.two_pi; + path + +type path = + | Empty + | Line of Point.t * Point.t + | Three_point of Point.t * Point.t * Point.t + | Curve of Curves.Bezier.t array + +type t = + { id : int + ; path : path } + +let move_to + : area:Gg.v2 -> Brr_canvas.C2d.Path.t -> path -> unit + = fun ~area canvaPath path -> + match path with + | Empty -> () + | Line (p0, _) + | Three_point (p0, _, _) -> + let x, y = translate_point ~area (Point.get_coord p0) in + Path.move_to canvaPath ~x ~y + | Curve beziers -> + try + let bezier = Array.get beziers 0 in + let x, y = translate_point ~area bezier.Curves.Bezier.p0 in + Path.move_to canvaPath ~x ~y + with _ -> () + +let draw + : ?connexion:Point.t -> area:Gg.v2 -> Brr_canvas.C2d.Path.t -> path -> unit + = fun ?connexion ~area canvaPath path -> + match connexion, path with + + | _, Empty -> () + | None, Line (_, p1) -> + ignore @@ line area ~p1 canvaPath + + | Some p0, Line (p1, p2) + | None, Three_point (p0, p1, p2) + | Some _, Three_point (p0, p1, p2) -> + ignore @@ three_points area ~p0 ~p1 ~p2 canvaPath + + | _, Curve beziers -> + Array.iter beziers + ~f:(fun bezier -> + + let cx, cy = translate_point ~area bezier.Curves.Bezier.ctrl0 + and cx', cy' = translate_point ~area bezier.Curves.Bezier.ctrl1 + and x, y = translate_point ~area bezier.Curves.Bezier.p1 in + + Path.ccurve_to canvaPath + ~cx ~cy + ~cx' ~cy' + ~x ~y + ) + +let go_back + : ?connexion:Point.t -> area:Gg.v2 -> Brr_canvas.C2d.Path.t -> path -> unit + = fun ?connexion ~area canvaPath path -> + let vect = Gg.V2.of_polar @@ Gg.V2.v + 0.02 + Gg.Float.pi_div_4 + in + match connexion, path with + | _, Empty -> () + | _, Three_point (p0, p1, p2) -> + let open Point in + let p0' = p0 + vect + and p1' = p1 + vect + and p2' = p2 + vect in + + let x, y = translate_point' ~area vect @@ Point.get_coord p2 in + Path.line_to canvaPath ~x ~y; + ignore @@ three_points area ~p0:p2' ~p1:p1' ~p2:p0' canvaPath + | _, Curve beziers -> + let last = Array.get beziers ((Array.length beziers) -1) in + + let x, y = + last.Curves.Bezier.p1 + |> translate_point' vect ~area in + + Path.line_to canvaPath ~x ~y; + + for i = 1 to Array.length beziers do + + let i = (Array.length beziers) - i in + let bezier = Array.get beziers i in + + let cx, cy = translate_point' vect ~area bezier.Curves.Bezier.ctrl1 + and cx', cy' = translate_point' vect ~area bezier.Curves.Bezier.ctrl0 + and x, y = translate_point' vect ~area bezier.Curves.Bezier.p0 in + + Path.ccurve_to canvaPath + ~cx ~cy + ~cx' ~cy' + ~x ~y + done; + let x, y = + (Array.get beziers 0).Curves.Bezier.p0 + |> translate_point' vect ~area in + Path.line_to canvaPath ~x ~y; + + | _ -> () + +type quick_path = Point.t list * Curves.Bezier.t list + +let id = ref 0 + +let to_path + : quick_path -> t + = fun (points, beziers) -> + + incr id; + let id = !id in + match beziers with + | [] -> + begin match points with + | p0::p1::[] -> {id; path=Line (p0, p1)} + | p0::p1::p2::[] -> {id; path=Three_point (p0, p1, p2)} + | points -> + + let (let*) v f = + match v with + | Ok beziers -> f beziers + | _ -> {id; path=Empty} in + + let points' = List.map ~f:Point.get_coord points in + + let* beziers = Curves.Bspline.to_bezier points' in + {id; path=Curve beziers} + end + | _ -> + let (let*) v f = + match v with + | Ok beziers -> f beziers + | _ -> {id; path=Curve (Array.of_list beziers)} in + + let connexion = match beziers with + | hd::_ -> Some hd.Curves.Bezier.p1 + | _ -> None in + + let* beziers' = Curves.Bspline.to_bezier + ?connexion1:connexion + (List.map points ~f:Point.get_coord) in + + + (* Create a new array with both lenght *) + let t = Array.append + beziers' + (Array.of_list beziers) + in + + {id; path = Curve t} diff --git a/draw/dune b/draw/dune new file mode 100755 index 0000000..1791604 --- /dev/null +++ b/draw/dune @@ -0,0 +1,8 @@ +(library + (name draw) + (libraries + gg + brr + curves + ) + ) diff --git a/draw/point.ml b/draw/point.ml new file mode 100755 index 0000000..150bc8e --- /dev/null +++ b/draw/point.ml @@ -0,0 +1,78 @@ +open StdLabels + +type t = + { p: Gg.v2 + ; size : float + ; angle: float + } + +let create x y = + { p = Gg.V2.v x y + ; size = 0.1 + ; angle = Gg.Float.pi_div_4 + } + +let (+) p1 p2 = + { p1 with p = Gg.V2.(+) p1.p p2 } + +let get_coord { p; _ } = p + +let get_coord' + : t -> Gg.v2 + = fun t -> + let open Gg.V2 in + let trans = of_polar @@ v t.size t.angle in + t.p + trans + +let return_segment + : Curves.Bezier.t -> Curves.Bezier.t list -> Curves.Bezier.t list + = fun bezier beziers -> + (* We gave the points in reverse order, so we have to revert the + curve *) + let bezier' = Curves.Bezier.reverse bezier in + bezier'::beziers + + +let get_new_segment connexion0 p5 p4 p3 p2 p1 = + let p5' = get_coord p5 + and p4' = get_coord p4 + and p3' = get_coord p3 + and p2' = get_coord p2 + and p1' = get_coord p1 in + + let points_to_link = + [ p1' + ; p2' + ; p3' + ; p4' + ; p5' ] in + Curves.Bspline.to_bezier ?connexion0 points_to_link + +let add_point_in_path + : float -> float -> t list -> Curves.Bezier.t list -> t list * Curves.Bezier.t list + = fun x y path beziers -> + let lastClick = create x y in + let (let*) v f = + match v with + | Ok bezier -> + if Array.length bezier > 0 then + f (Array.get bezier 0) + else + lastClick::path, beziers + | _ -> + lastClick::path, beziers + in + + let connexion0 = match beziers with + | hd::_ -> Some hd.Curves.Bezier.p1 + | _ -> None in + + match path with + | p4::p3::p2::p1::_ -> + let* bezier = get_new_segment connexion0 + lastClick p4 p3 p2 p1 in + (* We remove the last point and add the bezier curve in the list*) + let firsts = lastClick::p4::p3::p2::[] in + firsts, return_segment bezier beziers + | _ -> + lastClick::path, beziers diff --git a/draw/point.mli b/draw/point.mli new file mode 100755 index 0000000..8e3f5aa --- /dev/null +++ b/draw/point.mli @@ -0,0 +1,13 @@ +type t + +val (+): t -> Gg.v2 -> t + +val get_coord : t -> Gg.v2 + +val create: float -> float -> t + +val add_point_in_path + : float -> float -> t list -> Curves.Bezier.t list -> t list * Curves.Bezier.t list + +val get_coord' + : t -> Gg.v2 @@ -0,0 +1,25 @@ +(executables + (names script) + (libraries + js_of_ocaml + brr + brr.note + vg + vg.htmlc + messages + messages_json + worker + draw + curves + tools + events + ) + (modes js) + (preprocess (pps js_of_ocaml-ppx)) + (link_flags (:standard -no-check-prims)) + ) + +(rule + (targets script.js) + (deps script.bc.js) + (action (run cp %{deps} %{targets}))) diff --git a/dune-project b/dune-project new file mode 100755 index 0000000..45acd3f --- /dev/null +++ b/dune-project @@ -0,0 +1 @@ +(lang dune 2.7) diff --git a/events/dune b/events/dune new file mode 100755 index 0000000..68e2dd2 --- /dev/null +++ b/events/dune @@ -0,0 +1,7 @@ +(library + (name events) + (libraries + brr + brr.note + ) +) diff --git a/events/timer.ml b/events/timer.ml new file mode 100755 index 0000000..def9a81 --- /dev/null +++ b/events/timer.ml @@ -0,0 +1,24 @@ +type t = Brr.G.timer_id ref * unit Note.E.send + +let create + : unit -> (t * unit Note.E.t) + = fun () -> + let event, send = Note.E.create () in + (ref (-1), send), event + +let stop + : t -> unit + = fun (id, _) -> + Brr.G.stop_timer !id + +let start + : t -> float -> unit + = fun (id, send) d -> + + Brr.G.stop_timer !id; + let timer_id = Brr.G.set_interval + ~ms:(int_of_float @@ d *. 1000.) + (fun () -> send ()) in + ignore @@ Brr.G.set_timeout ~ms:0 send; + id:= timer_id; + diff --git a/events/timer.mli b/events/timer.mli new file mode 100755 index 0000000..4bf8a9b --- /dev/null +++ b/events/timer.mli @@ -0,0 +1,7 @@ +type t + +val create : unit -> t * unit Note.E.t + +val start: t -> float -> unit + +val stop: t -> unit diff --git a/index.html b/index.html new file mode 100755 index 0000000..26ba7fa --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, + initial-scale=1.0"> + <script type="text/javascript" defer="defer" src="_build/default/canva.bc.js"></script> + <style type="text/css"> body \{ background-color: black; margin: 3em; \}</style> + <title>Drawer</title> +</head> +<body> + <noscript>Sorry, you need to enable JavaScript to see this page.</noscript> +</body> +</html> diff --git a/matrix/EltsI.ml b/matrix/EltsI.ml new file mode 100755 index 0000000..fcfdb50 --- /dev/null +++ b/matrix/EltsI.ml @@ -0,0 +1,28 @@ +module type ORDERED_AND_OPERATIONAL = +sig + + (* Exception for from_string. Is raised when from_string is passed something + * that is not an elt *) + exception NonElt + + type t + + (* The zero element *) + val zero : t + + (* The one element *) + val one: t + + (* ts must be comparable *) + val compare : t -> t -> Order.order + + (* Basic mathematical operations must be possible *) + val add: t -> t -> t + + val subtract: t -> t -> t + + val multiply: t -> t -> t + + val divide: t -> t -> t + +end diff --git a/matrix/Helpers.ml b/matrix/Helpers.ml new file mode 100755 index 0000000..6980052 --- /dev/null +++ b/matrix/Helpers.ml @@ -0,0 +1,16 @@ +(* Takes in a string and a separator, and separates the string into a list of + * substrings where each substring is between two separators or between a + * separator and the beginning/end of the string *) +let explode (s: string) (space: string) : string list = + let rec build (curr: string) (buffer: string) (lst: string list) : string list = + let len = String.length curr in + if len = 0 then buffer::lst + else + let c = String.sub curr (len - 1) 1 in + if len = 1 then (c ^ buffer)::lst + else + let s' = String.sub curr 0 (len - 1) in + if c = space then build s' "" (buffer::lst) + else build s' (c ^ buffer) lst in + build (String.trim s) "" [] + diff --git a/matrix/Matrix.ml b/matrix/Matrix.ml new file mode 100755 index 0000000..7f1d54b --- /dev/null +++ b/matrix/Matrix.ml @@ -0,0 +1,529 @@ +open Order + +module Order = Order + +(*************** Exceptions ***************) + +exception NonSquare +exception ImproperDimensions + +(* Functor so we can Abstract away! *) +module MakeMatrix (C: EltsI.ORDERED_AND_OPERATIONAL) : + (MatrixI.MATRIX with type elt = C.t) = +struct + + + (*************** End Exceptions ***************) + + (*************** Types ***************) + + type elt = C.t + + (* A matrix is a pair of dimension (n x p) and a array of arrays + * the first array is the row (n) and the second the column (p) *) + type matrix = (int * int) * (elt array array) + + (*************** End Types ***************) + + (*************** Base Functions ***************) + + (* catching negative dimensions AND 0 dimensions and too large + * of a dimension so we don't have to worry about it later *) + let empty (rows: int) (columns: int) : matrix = + if rows > 0 && columns > 0 then + try + let m = Array.make_matrix rows columns C.zero in ((rows,columns),m) + with _ -> + raise ImproperDimensions + else (* dimension is negative or 0 *) + raise ImproperDimensions + + (*************** End Base Functions ***************) + + (*************** Helper Functions ***************) + + (* get's the nth row of a matrix and returns (r, row) where r is the length + * of the row and row is a COPY of the original row. For example, calling + * calling get_row m 1 will return (3, |1 3 4 |) + * ________ + * m = | 1 3 4 | + * |*2 5 6 | + *) + (* aside: we don't check whether n < 1 because of our matrix invariant *) + let get_row (((n,p),m): matrix) (row: int) : int * elt array = + if row <= n then + let row' = Array.map (fun x -> x) m.(row - 1) in + (p, row') + else + raise (Failure "Row out of bounds.") + + (* similar to get_row. For m, get_column m 1 will return (2, |1 2|) *) + let get_column (((n,p),m): matrix) (column: int) : int * elt array = + if column <= p then + begin + let column' = Array.make n C.zero in + for i = 0 to n - 1 do + column'.(i) <- m.(i).(column - 1) + done; + (n, column') + end + else + raise (Failure "Column out of bounds.") + + (* sets the nth row of the matrix m to the specified array a. + * This is done IN-PLACE. Therefore the function returns unit. You should + * nonetheless enfore immutability whenever possible. For a clarification on + * what nth row means, look at comment for get_row above. *) + let set_row (((n, p), m): matrix) (row: int) (a: elt array) : unit = + if row <= n then + begin + assert(Array.length a = p); + for i = 0 to p - 1 do + m.(row - 1).(i) <- a.(i) + done; + end + else + raise (Failure "Row out of bounds.") + + (* Similar to set_row but sets the nth column instead *) + let set_column (((n,p),m): matrix) (column: int) (a: elt array) : unit = + if column <= p then + begin + assert(Array.length a = n); + for i = 0 to n - 1 do + m.(i).(column - 1) <- a.(i) + done; + end + else + raise (Failure "Column out of bounds.") + + (* returns the ij-th element of a matrix (not-zero indexed) *) + let get_elt (((n,p),m): matrix) ((i,j): int*int) : elt = + if i <= n && j <= p then + m.(i - 1).(j - 1) + else + raise ImproperDimensions + + (* Changes the i,j-th element of a matrix to e. Is not zero-indexed, and + * changes the matrix in place *) + let set_elt (((n,p),m): matrix) ((i,j): int*int) (e: elt) : unit = + if i <= n && j <= p then + m.(i - 1).(j - 1) <- e + else + raise ImproperDimensions + + (* similar to map, but applies to function to the entire matrix + * Returns a new matrix *) + let map (f: elt -> elt) (mat: matrix) : matrix = + let (dim,m) = mat in + (dim, Array.map (Array.map f) m) + + (* Just some wrapping of Array.iter made for Matrices! *) + let iter (f: elt -> unit) (mat: matrix) : unit = + let _, m = mat in + Array.iter (Array.iter f) m + + (* Just some wrapping of Array.iteri. Useful for pretty + * printing matrix. The index is (i,j). NOT zero-indexed *) + let iteri (f: int -> int -> elt -> unit) (mat: matrix) : unit = + let _, m = mat in + Array.iteri (fun i row -> Array.iteri (fun j e -> f i j e) row) m + + (* folds over each row using base case u and function f *) + (* could be a bit more efficient? *) + let reduce (f: 'a -> elt -> 'a) (u: 'a) (((p,q),m): matrix) : 'a = + let total = ref u in + for i = 0 to p - 1 do + for j = 0 to q - 1 do + total := f (!total) m.(i).(j) + done; + done; + !total + + let fold_row ~(f: elt array -> 'b) ((_,m): matrix) : 'b list = + + let call_row acc v = (f v)::acc in + Array.fold_left call_row [] m + |> List.rev + + + + + (* given two arrays, this will calculate their dot product *) + (* It seems a little funky, but this is done for efficiency's sake. + * In short, it tries to multiply each element by it's respective + * element until the one array is indexed out of bounds. If the + * other array is also out of bounds, then it returns their value. + * Otherwise, the arrays were the wrong size and raises ImproperDimension + + THE ABOVE COMMENT HAS NOT BEEN IMPLEMENTED + + Instead we calculate the length before starting + *) + let dot (v1: elt array) (v2: elt array) : elt = + let rec dotting (i: int) (total: elt) : elt = + if i = 0 then total + else + let curr = C.multiply v1.(i-1) v2.(i-1) in + dotting (i - 1) (C.add curr total) in + let len1, len2 = Array.length v1, Array.length v2 in + if len1 = len2 then dotting len1 C.zero + else raise ImproperDimensions + + (* function to expose the dimensions of a matrix *) + let get_dimensions (m: matrix) : (int * int) = + let ((x,y), _) = m in (x,y) + + (*************** End Helper Functions ***************) + + + (*************** Primary Matrix Functions ***************) + + (* scales a matrix by the appropriate factor *) + let scale (m: matrix) (sc: elt) : matrix = map (C.multiply sc) m + + (* Generates a matrix from a list of lists. The inners lists are the rows *) + let from_list (lsts : elt list list) : matrix = + let check_length (length: int) (lst: elt list) : int = + if List.length lst = length then length + else raise ImproperDimensions in + let p = List.length lsts in + match lsts with + | [] -> raise ImproperDimensions + | hd::tl -> + let len = List.length hd in + if List.fold_left check_length len tl = len then + ((p,len),Array.map Array.of_list (Array.of_list lsts)) + else + raise ImproperDimensions + + (* Generates a matrix from a list of lists. The inners lists are the rows *) + let from_array (arrs : elt array array) : matrix = + let check_length (length: int) (arr: elt array) : unit = + if Array.length arr = length then () + else raise ImproperDimensions in + let p = Array.length arrs in + match Array.length arrs with + | 0 -> raise ImproperDimensions + | _ -> + let len = Array.length (Array.get arrs 0) in + Array.iter (check_length len) arrs; + ((p, len), arrs) + + (* Adds two matrices. They must have the same dimensions *) + let add ((dim1,m1): matrix) ((dim2,m2): matrix) : matrix = + if dim1 = dim2 then + let n, p = dim1 in + let (dim', sum_m) = empty n p in + for i = 0 to n - 1 do + for j = 0 to p - 1 do + sum_m.(i).(j) <- C.add m1.(i).(j) m2.(i).(j) + done; + done; + (dim',sum_m) + else + raise ImproperDimensions + + + (* Multiplies two matrices. If the matrices have dimensions m x n and p x q, n + * and p must be equal, and the resulting matrix will have dimension n x q *) + let mult (matrix1: matrix) (matrix2: matrix) : matrix = + let ((m,n), _), ((p,q), _) = matrix1, matrix2 in + if n = p then + let (dim, result) = empty m q in + for i = 0 to m - 1 do + for j = 0 to q - 1 do + let (_,row), (_,column) = get_row matrix1 (i + 1), + get_column matrix2 (j + 1) in + result.(i).(j) <- dot row column + done; + done; + (dim,result) + else + raise ImproperDimensions + + (*************** Helper Functions for Row Reduce ***************) + + (* + (* returns the index of the first non-zero elt in an array*) + let zero (arr: elt array) : int option = + let index = ref 1 in + let empty (i: int option) (e: elt) : int option = + match i, C.compare e C.zero with + | None, Equal -> (index := !index + 1; None) + | None, _ -> Some (!index) + | _, _ -> i in + Array.fold_left empty None arr + + (* returns the the location of the nth non-zero + * element in the matrix. Scans column wise. So the nth non-zero element is + * the FIRST non-zero element in the nth non-zero column *) + let nth_nz_location (m: matrix) (_: int): (int*int) option = + let ((n,p), _) = m in + let rec check_col (to_skip: int) (j: int) = + if j <= p then + let (_,col) = get_column m j in + match zero col with + | None -> check_col to_skip (j + 1) + | Some i -> + if to_skip = 0 then + Some (i,j) + else (* we want a later column *) + check_col (to_skip - 1) (j + 1) + else None in + check_col (n - 1) 1 + + (* returns the the location of the first + * non-zero and non-one elt. Scans column wise, from + * left to right. Basically, it ignores columns + * that are all zero or that *) + let fst_nz_no_loc (m: matrix): (int*int) option = + let ((_, p), _) = m in + let rec check_col (j: int) = + if j <= p then + let (_,col) = get_column m j in + match zero col with + | None -> check_col (j + 1) + | Some i -> + match C.compare col.(i-1) C.one with + | Equal -> check_col (j + 1) + | _ -> Some (i,j) + else None in + check_col 1 + *) + + (* Compares two elements in an elt array and returns the greater and its + * index. Is a helper function for find_max_col_index *) + let compare_helper (e1: elt) (e2: elt) (ind1: int) (ind2: int) : (elt*int) = + match C.compare e1 e2 with + | Equal -> (e2, ind2) + | Greater -> (e1, ind1) + | Less -> (e2, ind2) + + (* Finds the element with the greatest absolute value in a column. Is not + * 0-indexed. If two elements are both the maximum value, returns the one with + * the lowest index. Returns None if this element is zero (if column is all 0) + *) + let find_max_col_index (array1: elt array) (start_index: int) : int option = + let rec find_index (max_index: int) (curr_max: elt) (curr_index: int) + (arr: elt array) = + if curr_index = Array.length arr then + (if curr_max = C.zero then None + else Some (max_index+1)) (* Arrays are 0-indexed but matrices aren't *) + else + (match C.compare arr.(curr_index) C.zero with + | Equal -> find_index max_index curr_max (curr_index+1) arr + | Greater -> + (let (el, index) = compare_helper (arr.(curr_index)) + curr_max curr_index max_index in + find_index index el (curr_index+1) arr) + | Less -> + (let abs_curr_elt = C.subtract C.zero arr.(curr_index) in + let (el, index) = compare_helper abs_curr_elt curr_max curr_index + max_index in + find_index index el (curr_index+1) arr)) + in + find_index 0 C.zero (start_index -1) array1 + + (* Basic row operations *) + (* Scales a row by sc *) + let scale_row (m: matrix) (num: int) (sc: elt) : unit = + let (_, row) = get_row m num in + let new_row = Array.map (C.multiply sc) row in + set_row m num new_row + + (* Swaps two rows of a matrix *) + let swap_row (m: matrix) (r1: int) (r2: int) : unit = + let (len1, row1) = get_row m r1 in + let (len2, row2) = get_row m r2 in + let _ = assert (len1 = len2) in + let _ = set_row m r1 row2 in + let _ = set_row m r2 row1 in + () + + (* Subtracts a multiple of r2 from r1 *) + let sub_mult (m: matrix) (r1: int) (r2: int) (sc: elt) : unit = + let (len1, row1) = get_row m r1 in + let (len2, row2) = get_row m r2 in + let _ = assert (len1 = len2) in + for i = 0 to len1 - 1 do (* Arrays are 0-indexed *) + row1.(i) <- C.subtract row1.(i) (C.multiply sc row2.(i)) + done; + set_row m r1 row1 + + (*************** End Helper Functions for Row Reduce ***************) + + (* Returns the row reduced form of a matrix. Is not done in place, but creates + * a new matrix *) + let row_reduce (mat: matrix) : matrix = + let[@tailcall] rec row_reduce_h (n_row: int) (n_col: int) (mat2: matrix) : unit = + let ((num_row, _), _) = mat2 in + if (n_col = num_row + 1) then () + else + let (_,col) = get_column mat2 n_col in + match find_max_col_index col n_row with + | None (* Column all 0s *) -> row_reduce_h n_row (n_col+1) mat2 + | Some index -> + begin + swap_row mat2 index n_row; + let pivot = get_elt mat2 (n_row, n_col) in + scale_row mat2 (n_row) (C.divide C.one pivot); + for i = 1 to num_row do + if i <> n_row then sub_mult mat2 i n_row (get_elt mat2 (i,n_col)) + done; + row_reduce_h (n_row+1) (n_col+1) mat2 + end + in + (* Copies the matrix *) + let ((n,p),m) = mat in + let (dim,mat_cp) = empty n p in + for i = 0 to n - 1 do + for j = 0 to p - 1 do + mat_cp.(i).(j) <- m.(i).(j) + done; + done; + let _ = row_reduce_h 1 1 (dim,mat_cp) in (dim,mat_cp) + + (*************** End Main Functions ***************) + + (*************** Optional module functions ***************) + + (* calculates the trace of a matrix *) + let trace (((n,p),m): matrix) : elt = + let rec build (elt: elt) (i: int) = + if i > -1 then + build (C.add m.(i).(i) elt) (i - 1) + else + elt in + if n = p then build C.zero (n - 1) + else raise ImproperDimensions + + (* calculates the transpose of a matrix and retuns a new one *) + let transpose (((n,p),m): matrix) = + let (dim,m') = empty p n in + for i = 0 to n - 1 do + for j = 0 to p - 1 do + m'.(j).(i) <- m.(i).(j) + done; + done; + assert(dim = (p,n)); + ((p,n),m') + + (* Returns the inverse of a matrix. Uses a pretty simple algorithm *) + let inverse (mat: matrix) : matrix = + let ((n, p), _) = mat in + if n = p then + (* create augmented matrix *) + let augmented = empty n (2*n) in + for i = 1 to n do + let (dim,col) = get_column mat i in + let arr = Array.make n C.zero in + begin + assert(dim = n); + arr.(i-1) <- C.one; + set_column augmented i col; + set_column augmented (n + i) arr + end + done; + let augmented' = row_reduce augmented in + (* create the inverted matrix and fill in with appropriate values *) + let inverse = empty n n in + for i = 1 to n do + let (dim, col) = get_column augmented' (n + i) in + let _ = assert(dim = n) in + let _ = set_column inverse i col in + () + done; + inverse + else + raise NonSquare + + (***************** HELPER FUNCTIONS FOR DETERMINANT *****************) + (* creates an identity matrix of size n*) + let create_identity (n:int) : matrix = + let (dim,m) = empty n n in + for i = 0 to n - 1 do + m.(i).(i) <- C.one + done; + (dim,m) + + (* Finds the index of the maximum value of an array *) + let find_max_index (arr: elt array) (start_index : int) : int = + let rec find_index (max_index: int) (curr_index: int) = + if curr_index = Array.length arr then max_index+1 + else + match C.compare arr.(curr_index) arr.(max_index) with + | Equal | Less -> find_index max_index (curr_index + 1) + | Greater -> find_index curr_index (curr_index + 1) in + find_index (start_index - 1) start_index + + (* Creates the pivoting matrix for A. Returns swqps. Adapted from + * http://rosettacode.org/wiki/LU_decomposition#Common_Lisp *) + let pivotize (((n,p),m): matrix) : matrix * int = + if n = p then + let swaps = ref 0 in + let pivot_mat = create_identity n in + for j = 1 to n do + let (_,col) = get_column ((n,p),m) j in + let max_index = find_max_index col j in + if max_index <> j then + (swaps := !swaps + 1; swap_row pivot_mat max_index j) + else () + done; + (pivot_mat,!swaps) + else raise ImproperDimensions + + (* decomposes a matrix into a lower triangualar, upper triangualar + * and a pivot matrix. It returns (L,U,P). Adapted from + * http://rosettacode.org/wiki/LU_decomposition#Common_Lisp *) + let lu_decomposition (((n,p),m): matrix) : (matrix*matrix*matrix)*int = + if n = p then + let mat = ((n,p),m) in + let lower, upper, (pivot,s) = empty n n, empty n n, pivotize mat in + let (_ ,l),(_ ,u), _ = lower,upper,pivot in + let ((_, _),mat') = mult pivot mat in + for j = 0 to n - 1 do + l.(j).(j) <- C.one; + for i = 0 to j do + let sum = ref C.zero in + for k = 0 to i - 1 do + sum := C.add (!sum) (C.multiply u.(k).(j) l.(i).(k)) + done; + u.(i).(j) <- C.subtract mat'.(i).(j) (!sum) + done; + for i = j to n - 1 do + let sum = ref C.zero in + for k = 0 to j - 1 do + sum := C.add (!sum) (C.multiply u.(k).(j) l.(i).(k)) + done; + let sub = C.subtract mat'.(i).(j) (!sum) in + l.(i).(j) <- C.divide sub u.(j).(j) + done; + done; + (lower,upper,pivot),s + else raise ImproperDimensions + + (* Computes the determinant of a matrix *) + let determinant (m: matrix) : elt = + try + let ((n,p), _) = m in + if n = p then + let rec triangualar_det (a,mat) curr_index acc = + if curr_index < n then + let acc' = C.multiply mat.(curr_index).(curr_index) acc in + triangualar_det (a,mat) (curr_index + 1) acc' + else acc in + let ((dim1,l),(dim2,u), _),s = lu_decomposition m in + let det1, det2 = triangualar_det (dim1,l) 0 C.one, + triangualar_det (dim2,u) 0 C.one in + if s mod 2 = 0 then C.multiply det1 det2 + else C.subtract C.zero (C.multiply det1 det2) + else raise ImproperDimensions + with + | _ -> C.zero + + + (*************** Optional module functions ***************) + + +end diff --git a/matrix/MatrixI.ml b/matrix/MatrixI.ml new file mode 100755 index 0000000..fbc4e21 --- /dev/null +++ b/matrix/MatrixI.ml @@ -0,0 +1,105 @@ +exception NonSquare +exception ImproperDimensions + +module type MATRIX = +sig + + (******** TYPES ********) + type elt + + type matrix + + (* empty matrix of nxp dimensions *) + val empty : int -> int -> matrix + + (* Takes a list of lists and converts that to a matrix *) + val from_list : (elt list list) -> matrix + + val from_array: elt array array -> matrix + + (******** OPERATIONS ON ONE MATRIX ********) + (* Takes in a matrix and returns its dimensions. ie, nxp *) + val get_dimensions : matrix -> (int * int) + + (* get's the row of a matrix: Not zero-indexed. *) + val get_row : matrix -> int -> (int * elt array) + + (* similar to get_row *) + val get_column: matrix -> int -> (int * elt array) + + (* sets the row of a matrix in place! Not zero-index *) + val set_row: matrix -> int -> elt array -> unit + + (* similar to set_row, but for a column *) + val set_column: matrix -> int -> elt array -> unit + + (* gets the element at the specified index. *) + val get_elt: matrix -> (int * int) -> elt + + (* sets the element at the specified index *) + val set_elt: matrix -> (int * int) -> elt -> unit + + (* Scales every element in the matrix by another elt *) + val scale : matrix -> elt -> matrix + + + (******** MORE ADVANCED SINGLE MATRIX OPERATIONS ********) + (* Returns the row reduced form of a matrix *) + val row_reduce: matrix -> matrix + (* We will implement the algorithm found in the link above *) + + (* Returns the inverse of a matrix *) + val inverse: matrix -> matrix + + (*Transposes a matrix. If the input has dimensions m x n, the output will + * have dimensions n x m *) + val transpose: matrix -> matrix + + (* Returns the trace of the matrix *) + val trace: matrix -> elt + + (******** OPERATIONS ON TWO MATRICES ********) + (* Adds two matrices. They must have the same dimensions *) + val add : matrix -> matrix -> matrix + + (* Multiplies two matrices. If the matrices have dimensions m x n and p x q, n + * and p must be equal, and the resulting matrix will have dimension m x q *) + val mult: matrix -> matrix -> matrix + + (**** Other Library Functions ***) + (* Function to make over our matrices *) + val map : (elt -> elt) -> matrix -> matrix + + (*val iter : (elt -> unit) -> matrix -> unit*) + + (* Returns the LUP decomposition of a matrix *) + val lu_decomposition : matrix -> (matrix * matrix * matrix) * int + + (* Returns the determinant of the matrix *) + val determinant: matrix -> elt + + (************** Other Library Functions *************) + val iter : (elt -> unit) -> matrix -> unit + + val iteri : (int -> int -> elt -> unit) -> matrix -> unit + + (* folds over each row using base case u and function f *) + val reduce: ('a -> elt -> 'a) -> 'a -> matrix -> 'a + + val fold_row: f:(elt array -> 'b) -> matrix -> 'b list + + (********** Specific for Simplex Algorithm ***********) + (** All of the following functions will raise ImproperDimensions + * Exception if the matrix is not the right size for the operation + **) + + (* Scales a row *) + val scale_row: matrix -> int -> elt -> unit + + (* Swaps two rows *) + val swap_row: matrix -> int -> int -> unit + + (* Subtracts a multiple of one row (the 2nd int) from another (the 1st int) *) + val sub_mult: matrix -> int -> int -> elt -> unit + +end diff --git a/matrix/Order.ml b/matrix/Order.ml new file mode 100755 index 0000000..5f2aa22 --- /dev/null +++ b/matrix/Order.ml @@ -0,0 +1,2 @@ +(* Defines a general ordering type *) +type order = Equal | Less | Greater diff --git a/matrix/dune b/matrix/dune new file mode 100755 index 0000000..1c0cab6 --- /dev/null +++ b/matrix/dune @@ -0,0 +1,3 @@ +(library + (name matrix) +) diff --git a/script.html b/script.html new file mode 100755 index 0000000..cd40d37 --- /dev/null +++ b/script.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, + initial-scale=1.0"> + <style type="text/css"> + html, +body { + margin: 0; + padding: 0; +} + +body { + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; + line-height: 1.4em; + background: #f5f5f5; + color: #4d4d4d; + min-width: 230px; + max-width: 550px; + margin: 0 auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 300; +} + + </style> + <title>Drawer</title> +</head> +<body> + <noscript>Sorry, you need to enable JavaScript to see this page.</noscript> + <script id="drawer_js" type="text/javascript" defer="defer" src="script.js"></script> + <script> + var script = document.getElementById('drawer_js'); + script.addEventListener('load', function() { + var app = document.getElementById('slate'); + drawer.run(app); + }); + </script> + + + <section class="todoapp" id="app"> + <canvas id="slate" class="drawing-zone" width="800" height="800"> + </canvas> + </section> + <footer class="info"> </footer> +</body> +</html> diff --git a/script.ml b/script.ml new file mode 100755 index 0000000..f97eed2 --- /dev/null +++ b/script.ml @@ -0,0 +1,275 @@ +open StdLabels +open Note +open Brr + +module Timer = Events.Timer + +module Point = Draw.Point + +type mode = + | Edit + | Out + +type current = + { points : Point.t list (* The list of points to draw *) + ; beziers : Curves.Bezier.t list (* All the points already fixed *) + } + +type state = + { mode : mode + ; paths : Draw.t list (* All the previous paths *) + ; current : current + ; timer : Timer.t + } + +(** Events *) +type canva_events = + [ `Click of float * float + | `Out of float * float + ] + +type events = + [ canva_events + | `Point of float * float ] + +type canva_signal = Point.t + +module Mouse = Brr_note_kit.Mouse + +(** Create the element in the page, and the event handler *) +let canva + : Brr.El.t -> [> canva_events] Note.E.t * (float * float) option Note.S.t * Brr_canvas.Canvas.t + = fun element -> + let module C = Brr_canvas.Canvas in + let c = C.of_el element in + + (* Mouse events *) + let mouse = Brr_note_kit.Mouse.on_el + (fun x y -> (x, y)) element in + + let click = + Brr_note_kit.Mouse.left_down mouse + |> E.map (fun c -> `Click c) in + + let up = + Brr_note_kit.Mouse.left_up mouse + |> E.map (fun c -> `Out c) in + + let position = Mouse.pos mouse in + + let pos = S.l2 (fun b pos -> + if b then + Some pos + else + None + ) (Mouse.left mouse) position in + + E.select [click; up], pos, c + +let do_action + : events -> state -> state + = fun event state -> + match event, state.mode with + | `Point (x, y), Edit -> + (* Add the point in the list *) + let points, beziers = Point.add_point_in_path + x y + state.current.points + state.current.beziers in + + let current= {points; beziers} in + + { state with current } + | `Click _, Out -> + Timer.start state.timer 0.3; + { state with mode = Edit } + | `Out (x, y), _ -> + Timer.stop state.timer; + (* Add the point in the list *) + let points, beziers = Point.add_point_in_path + x y + state.current.points + state.current.beziers in + let beziers = Draw.to_path (points, beziers) in + + let paths = beziers::state.paths + and current = { points = []; beziers = []} in + { state with mode = Out; paths; current } + | _ -> state + +let backgroundColor = Jstr.v "#2e3440" +let white = Jstr.v "#eceff4" +let green = Jstr.v "#a3be8c" +let nord8 = Jstr.v "#81a1c1" + +let draw + : ?connexion:Gg.v2 -> area:Gg.v2 -> Point.t list -> Brr_canvas.C2d.Path.t + = fun ?connexion ~area points -> + + let open Brr_canvas.C2d in + let path = Path.create () in + + + let () = match points with + | [] -> () + | hd::_ -> + let vect = Draw.Line (hd, Point.create 0. 0.) in + Draw.move_to ~area path vect in + + let _ = match points with + | [] + | _::[] -> () + | _::p1::[] -> + Draw.line area ~p1 path + | p0::p1::p2::[] -> + Draw.three_points area ~p0 ~p1 ~p2 path + | _ -> + Draw.multi_points ?connexion area points path + in path + +let draw_path area points beziers = + let open Brr_canvas.C2d in + let connexion = match beziers with + | [] -> None + | hd ::_ -> Some hd.Curves.Bezier.p1 in + (* Firt draw all the points most recent points *) + let path = draw ?connexion ~area points in + + (* Then add the fixed ones *) + let path = List.fold_left beziers + ~init:path + ~f:(fun path bezier -> + + let cx, cy = Draw.translate_point ~area bezier.Curves.Bezier.ctrl0 + and cx', cy' = Draw.translate_point ~area bezier.Curves.Bezier.ctrl1 + and x, y = Draw.translate_point ~area bezier.Curves.Bezier.p1 in + + Path.ccurve_to path + ~cx ~cy + ~cx' ~cy' + ~x ~y; + path + ) in + path + +let on_change canva mouse_position state = + let open Brr_canvas.C2d in + + let w, h = Brr_canvas.Canvas.(float_of_int @@ w canva, float_of_int @@ h canva) in + let area = Gg.V2.v w h in + + let context = create canva in + + set_fill_style context (color backgroundColor); + fill_rect context + ~x:0.0 + ~y:0.0 + ~w + ~h; + set_stroke_style context (color white); + set_fill_style context (color white); + + (* If we are in edit mode, we add a point under the cursor. + + Otherwise, we would only display the previous registered point, which can + be far away in the past, and would give to the user a sensation of lag. + + *) + let pos = S.rough_value mouse_position in + let points = + match state.mode, pos with + | Edit, Some (x, y) -> (Point.create x y)::state.current.points + | _ -> state.current.points in + + let path = draw_path area (points) state.current.beziers in + stroke context path; + + List.iter state.paths + ~f:(fun path -> + let p = Path.create () in + Draw.move_to ~area p path.Draw.path; + Draw.draw ~area p path.Draw.path; + Draw.go_back ~area p path.Draw.path; + fill ~fill_rule:Fill_rule.nonzero context p; + +(* + match path.Draw.path with + | Curve c -> + let c' = Array.init + (Array.length c) + ~f:(fun i -> + Curves.Bezier.reverse @@ Array.get c ((Array.length c) -i - 1) + ) + in + let p' = Draw.Curve c' in + Draw.move_to ~area p p'; + Draw.draw ~area p p'; + Draw.go_back ~area p p'; + fill ~fill_rule:Fill_rule.nonzero context p; + () + | _ -> () +*) + + ); + () + + +let page_main id = + + let timer, tick = Timer.create () in + + let init = + { paths = [] + ; current = { points = [] + ; beziers = [] } + ; mode = Out + ; timer + } in + + (*begin match Document.find_el_by_id G.document id with*) + begin match (Jv.is_none id) with + | true -> Console.(error [str "No element with id '%s' found"; id]) + | false -> + + let canva_events, mouse_position, canva = canva (Jv.Id.of_jv id) in + + let tick_event = + S.sample_filter mouse_position + ~on:tick + (fun pos () -> Option.map (fun p -> `Point p) pos ) in + + (* The first evaluation is the state. Which is the result of all the + successives events to the initial state *) + let state = + E.select [canva_events; tick_event] + |> E.map do_action + |> Note.S.accum init in + + (* The seconde evaluation is the canva refresh, which only occurs when + the mouse is updated *) + let v = + E.map (fun _ -> state) (S.changes mouse_position) + |> E.map (fun x -> on_change canva mouse_position (S.value x) ) + |> fun ev -> E.log ev (fun _ -> ()) in + + (* Draw the canva for first time *) + on_change canva mouse_position init; + + let _ = Logr.hold (S.log state (fun _ -> ())) in + let _ = match v with + | None -> () + | Some log -> Logr.hold log in + () + + end + +let () = + if Brr_webworkers.Worker.ami () then + () + else + let open Jv in + let drawer = obj + [| "run", (repr page_main) + |] in + + set global "drawer" drawer diff --git a/theme/dune b/theme/dune new file mode 100755 index 0000000..a812bef --- /dev/null +++ b/theme/dune @@ -0,0 +1,7 @@ +(library + (name theme) + (libraries + color + gg + ) + ) diff --git a/theme/nord.ml b/theme/nord.ml new file mode 100755 index 0000000..4748d83 --- /dev/null +++ b/theme/nord.ml @@ -0,0 +1,39 @@ +open StdLabels + +let default = Color.of_rgb 5 255 255 + +let theme = + [| "#2e3440" + ; "#3b4252" + ; "#434c5e" + ; "#4c566a" + (* Bright *) + ; "#d8dee9" + ; "" + ; "#eceff4" + (* Frost *) + ; "#8fbcbb" + ; "" + ; "" + ; "" + (* Aurora 11 - *) + ; "#bf616a" (* Redd color *) + ; "" + ; "" + ; "#a3be8c" (* Green color *) + + + |] + |> Array.map ~f:(fun f -> + Color.of_hexstring f + |> Option.value ~default + ) + +let set_color t f = + let Color.Rgba'.{r; g; b; _ } = Color.to_rgba' (Array.get theme t) in + f r g b + + +let to_gg t = + let Color.Rgba'.{r; g; b; _ } = Color.to_rgba' (Array.get theme t) in + Gg.Color.of_srgb (Gg.V4.v r g b 1.) diff --git a/theme/theme.ml b/theme/theme.ml new file mode 100755 index 0000000..39974b1 --- /dev/null +++ b/theme/theme.ml @@ -0,0 +1 @@ +module Nord = Nord diff --git a/tools/dune b/tools/dune new file mode 100755 index 0000000..a2c3fdb --- /dev/null +++ b/tools/dune @@ -0,0 +1,6 @@ +(library + (name tools) + (libraries + gg + ) + ) diff --git a/tools/utils.ml b/tools/utils.ml new file mode 100755 index 0000000..b8a473f --- /dev/null +++ b/tools/utils.ml @@ -0,0 +1,63 @@ +open Gg.V2 + +let norm_angle vect = + mod_float + ((angle vect) +. Gg.Float.two_pi) + Gg.Float.two_pi + + +let intersection + : (Gg.v2 * Gg.v2) -> (Gg.v2 * Gg.v2) -> Gg.v2 option + = fun (p0, p1) (p2, p3) -> + let i = p1 - p0 + and j = p3 - p2 in + + let d = (x i *. y j) -. (y i *. x j) in + if Float.( (abs d) <= 0.01 ) then + None + else + let m = ((x i) *. (y p0) + -. (x i) *. (y p2) + -. (y i) *. (x p0) + +. (y i) *. (x p2)) /. d in + Some (p2 + m * j) + (* + let k = ((x j) *. (y p0) + -. (x j) *. (y p2) + -. (y j) *. (x p0) + +. (y j) *. (x p2)) /. d in + Some (p0 + k * i) + *) + + +let center + : Gg.v2 -> Gg.v2 -> Gg.v2 -> Gg.v2 option + = fun p0 p1 p2 -> + (* deltas *) + let d1 = p1 - p0 + and d2 = p2 - p1 in + + (* perpendiculars *) + let d1p = ortho d1 + and d2p = ortho d2 in + + (* Chord midpoints *) + let m1 = half (p0 + p1) + and m2 = half (p1 + p2) in + + (* midpoint offsets *) + let m1n = m1 + d1p + and m2n = m2 + d2p in + + intersection (m1, m1n) (m2, m2n) + +let rotate + : Gg.v2 -> float -> Gg.v2 + = fun p0 theta -> + let r = x (to_polar p0) in + of_polar (v r theta) + +let equal_point + : float -> Gg.v2 -> Gg.v2 -> bool + = fun eps p0 p1 -> + Gg.V2.equal_f (fun v0 v1 -> (Float.abs (v1 -. v0)) <= eps ) p0 p1 diff --git a/tools/utils.mli b/tools/utils.mli new file mode 100755 index 0000000..4e12906 --- /dev/null +++ b/tools/utils.mli @@ -0,0 +1,21 @@ +(** Return a normalize angle *) +val norm_angle + : Gg.v2 -> float + +(** return the interesction for two segments *) +val intersection + : (Gg.v2 * Gg.v2) -> (Gg.v2 * Gg.v2) -> Gg.v2 option + +(** Return the center of the cercle for three points + None if the point cannot be evaluated +*) +val center + : Gg.v2 -> Gg.v2 -> Gg.v2 -> Gg.v2 option + +(** Rotate the vector by the given angle *) +val rotate + : Gg.v2 -> float -> Gg.v2 + +(** Test equality between two points *) +val equal_point + : float -> Gg.v2 -> Gg.v2 -> bool diff --git a/worker/dune b/worker/dune new file mode 100755 index 0000000..9f95cf8 --- /dev/null +++ b/worker/dune @@ -0,0 +1,9 @@ +(library + (name worker) + (libraries + gg + brr + note + curves + ) + ) diff --git a/worker/worker.ml b/worker/worker.ml new file mode 100755 index 0000000..7a8d09a --- /dev/null +++ b/worker/worker.ml @@ -0,0 +1,5 @@ +open Brr_webworkers + +let spawn_worker name = + try Ok (Worker.create name) with + | Jv.Error e -> Error e |