aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSébastien Dailly <sebastien@chimrod.com>2020-12-16 14:39:42 +0100
committerSébastien Dailly <sebastien@chimrod.com>2020-12-16 14:39:42 +0100
commit4f262d6540281487f79870aff589ca92f5d2f6c6 (patch)
tree940e59d943715366d1aa72bb93f248dcd65ab992
Initial commit
-rwxr-xr-x.gitignore3
-rwxr-xr-xcurves/bezier.ml197
-rwxr-xr-xcurves/bezier.mli40
-rwxr-xr-xcurves/bspline.ml149
-rwxr-xr-xcurves/bspline.mli24
-rwxr-xr-xcurves/dd_splines.pdfbin0 -> 184248 bytes
-rwxr-xr-xcurves/dune7
-rwxr-xr-xdraw/draw.ml233
-rwxr-xr-xdraw/dune8
-rwxr-xr-xdraw/point.ml78
-rwxr-xr-xdraw/point.mli13
-rwxr-xr-xdune25
-rwxr-xr-xdune-project1
-rwxr-xr-xevents/dune7
-rwxr-xr-xevents/timer.ml24
-rwxr-xr-xevents/timer.mli7
-rwxr-xr-xindex.html14
-rwxr-xr-xmatrix/EltsI.ml28
-rwxr-xr-xmatrix/Helpers.ml16
-rwxr-xr-xmatrix/Matrix.ml529
-rwxr-xr-xmatrix/MatrixI.ml105
-rwxr-xr-xmatrix/Order.ml2
-rwxr-xr-xmatrix/dune3
-rwxr-xr-xscript.html48
-rwxr-xr-xscript.ml275
-rwxr-xr-xtheme/dune7
-rwxr-xr-xtheme/nord.ml39
-rwxr-xr-xtheme/theme.ml1
-rwxr-xr-xtools/dune6
-rwxr-xr-xtools/utils.ml63
-rwxr-xr-xtools/utils.mli21
-rwxr-xr-xworker/dune9
-rwxr-xr-xworker/worker.ml5
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
new file mode 100755
index 0000000..2618162
--- /dev/null
+++ b/curves/dd_splines.pdf
Binary files differ
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
diff --git a/dune b/dune
new file mode 100755
index 0000000..36edb56
--- /dev/null
+++ b/dune
@@ -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