From 42c3c122c4f53dd68bcdd89411835887c3ae0af9 Mon Sep 17 00:00:00 2001 From: Sébastien Dailly Date: Mon, 11 Jan 2021 11:33:32 +0100 Subject: Outline module --- layer/ductusEngine.ml | 82 ++++++++++++ layer/ductusEngine.mli | 2 + layer/ductusPrinter.ml | 82 ------------ layer/ductusPrinter.mli | 2 - layer/fillEngine.ml | 89 +++++++++++++ layer/fillEngine.mli | 2 + layer/fillPrinter.ml | 89 ------------- layer/fillPrinter.mli | 2 - layer/lineEngine.ml | 68 ++++++++++ layer/lineEngine.mli | 2 + layer/linePrinter.ml | 68 ---------- layer/linePrinter.mli | 2 - layer/paths.ml | 131 ++++++++++--------- layer/repr.ml | 2 +- path/fixed.ml | 188 ++++++++++++--------------- path/fixed.mli | 4 - script.it/dune | 10 ++ script.it/outline.ml | 12 ++ script.it/script.ml | 22 ++-- script.it/selection.ml | 26 ++-- script.it/selection.mli | 6 +- script.it/state.ml | 114 ++++++++++------ script.it/worker.ml | 42 ++++-- script.it/worker_messages/dune | 1 + script.it/worker_messages/worker_messages.ml | 5 +- 25 files changed, 562 insertions(+), 491 deletions(-) create mode 100755 layer/ductusEngine.ml create mode 100755 layer/ductusEngine.mli delete mode 100755 layer/ductusPrinter.ml delete mode 100755 layer/ductusPrinter.mli create mode 100755 layer/fillEngine.ml create mode 100755 layer/fillEngine.mli delete mode 100755 layer/fillPrinter.ml delete mode 100755 layer/fillPrinter.mli create mode 100755 layer/lineEngine.ml create mode 100755 layer/lineEngine.mli delete mode 100755 layer/linePrinter.ml delete mode 100755 layer/linePrinter.mli create mode 100755 script.it/outline.ml diff --git a/layer/ductusEngine.ml b/layer/ductusEngine.ml new file mode 100755 index 0000000..b943467 --- /dev/null +++ b/layer/ductusEngine.ml @@ -0,0 +1,82 @@ +module Make(Layer: Repr.PRINTER) = struct + + type point = Path.Point.t + + type t = + { path: (Layer.t) + } + + type repr = Layer.t + + let create_path + : 'b -> t + = fun _ -> + { path = Layer.create () + } + + let start + : point -> point -> t -> t + = fun p1 p2 { path } -> + let path = + Layer.move_to (Path.Point.get_coord p1) path + |> Layer.line_to (Path.Point.get_coord p2) in + { path + } + + let line_to + : (point * point) -> (point * point) -> t -> t + = fun (_, p1) (_, p1') {path} -> + let path = Layer.move_to (Path.Point.get_coord p1) path in + let path = Layer.line_to (Path.Point.get_coord p1') path in + { path + } + + let quadratic_to + : (point * Gg.v2 * Gg.v2 * point) -> (point * Gg.v2 * Gg.v2 * point) -> t -> t + = fun (p0, ctrl0, ctrl1, p1) (p0', ctrl0', ctrl1', p1') {path} -> + + let bezier = + { Shapes.Bezier.p0 = Path.Point.get_coord p0 + ; ctrl0 + ; ctrl1 + ; p1 = Path.Point.get_coord p1 + } + + and bezier' = + { Shapes.Bezier.p0 = Path.Point.get_coord p0' + ; ctrl0 = ctrl0' + ; ctrl1 = ctrl1' + ; p1 = Path.Point.get_coord p1' + } in + + (* Mark each point on the bezier curve. The first point is the most + recent point *) + let delay = + ((Path.Point.get_stamp p0) -. (Path.Point.get_stamp p1)) + *. 30. + in + let path = ref path in + for i = 0 to ((Int.of_float delay) ) do + let ratio = (Float.of_int i) /. delay in + let bezier, _ = Shapes.Bezier.slice ratio bezier + and bezier', _ = Shapes.Bezier.slice ratio bezier' in + + let point = Path.Point.mix ratio bezier.Shapes.Bezier.p1 p0 p1 + and point' = Path.Point.mix ratio bezier'.Shapes.Bezier.p1 p0' p1' in + + path := Layer.move_to (Path.Point.get_coord point) !path; + path := Layer.line_to (Path.Point.get_coord point') !path; + done; + + { path = !path } + + let stop + : t -> t + = fun path -> path + + + let get + : t -> Layer.t + = fun {path; _} -> + path +end diff --git a/layer/ductusEngine.mli b/layer/ductusEngine.mli new file mode 100755 index 0000000..e1660f4 --- /dev/null +++ b/layer/ductusEngine.mli @@ -0,0 +1,2 @@ +module Make(R:Repr.PRINTER): + Repr.ENGINE with type repr = R.t diff --git a/layer/ductusPrinter.ml b/layer/ductusPrinter.ml deleted file mode 100755 index db34481..0000000 --- a/layer/ductusPrinter.ml +++ /dev/null @@ -1,82 +0,0 @@ -module Make(Repr: Repr.PRINTER) = struct - - type point = Path.Point.t - - type t = - { path: (Repr.t) - } - - type repr = Repr.t - - let create_path - : 'b -> t - = fun _ -> - { path = Repr.create () - } - - let start - : point -> point -> t -> t - = fun p1 p2 { path } -> - let path = - Repr.move_to (Path.Point.get_coord p1) path - |> Repr.line_to (Path.Point.get_coord p2) in - { path - } - - let line_to - : (point * point) -> (point * point) -> t -> t - = fun (_, p1) (_, p1') {path} -> - let path = Repr.move_to (Path.Point.get_coord p1) path in - let path = Repr.line_to (Path.Point.get_coord p1') path in - { path - } - - let quadratic_to - : (point * Gg.v2 * Gg.v2 * point) -> (point * Gg.v2 * Gg.v2 * point) -> t -> t - = fun (p0, ctrl0, ctrl1, p1) (p0', ctrl0', ctrl1', p1') {path} -> - - let bezier = - { Shapes.Bezier.p0 = Path.Point.get_coord p0 - ; ctrl0 - ; ctrl1 - ; p1 = Path.Point.get_coord p1 - } - - and bezier' = - { Shapes.Bezier.p0 = Path.Point.get_coord p0' - ; ctrl0 = ctrl0' - ; ctrl1 = ctrl1' - ; p1 = Path.Point.get_coord p1' - } in - - (* Mark each point on the bezier curve. The first point is the most - recent point *) - let delay = - ((Path.Point.get_stamp p0) -. (Path.Point.get_stamp p1)) - *. 30. - in - let path = ref path in - for i = 0 to ((Int.of_float delay) ) do - let ratio = (Float.of_int i) /. delay in - let bezier, _ = Shapes.Bezier.slice ratio bezier - and bezier', _ = Shapes.Bezier.slice ratio bezier' in - - let point = Path.Point.mix ratio bezier.Shapes.Bezier.p1 p0 p1 - and point' = Path.Point.mix ratio bezier'.Shapes.Bezier.p1 p0' p1' in - - path := Repr.move_to (Path.Point.get_coord point) !path; - path := Repr.line_to (Path.Point.get_coord point') !path; - done; - - { path = !path } - - let stop - : t -> t - = fun path -> path - - - let get - : t -> Repr.t - = fun {path; _} -> - path -end diff --git a/layer/ductusPrinter.mli b/layer/ductusPrinter.mli deleted file mode 100755 index cdcaa7c..0000000 --- a/layer/ductusPrinter.mli +++ /dev/null @@ -1,2 +0,0 @@ -module Make(R:Repr.PRINTER): - Repr.LAYER with type repr = R.t diff --git a/layer/fillEngine.ml b/layer/fillEngine.ml new file mode 100755 index 0000000..9a3fe7e --- /dev/null +++ b/layer/fillEngine.ml @@ -0,0 +1,89 @@ +module Make(Layer: Repr.PRINTER) = struct + + type point = Path.Point.t + + type repr = Layer.t + + type t = + { path: Layer.t + ; close : Layer.t -> Layer.t + } + + let create_path + : (Layer.t -> Layer.t) -> t + = fun f -> + { close = f + ; path = Layer.create () + } + + (* Start a new path. *) + + let start + : point -> point -> t -> t + = fun p1 _ {close ; path } -> + let path = Layer.move_to (Path.Point.get_coord p1) path in + { close + ; path + } + + let line_to + : (point * point) -> (point * point) -> t -> t + = fun (p0, p1) (p0', p1') t -> + + let p0 = Path.Point.get_coord p0 + and p1 = Path.Point.get_coord p1 + and p0' = Path.Point.get_coord p0' + and p1' = Path.Point.get_coord p1' in + + let path = + Layer.move_to p1 t.path + |> Layer.line_to p1' + |> Layer.line_to p0' + |> Layer.line_to p0 + |> Layer.line_to p1 + |> Layer.close in + let path = t.close path in + { t with path} + + let quadratic_to + : (point * Gg.v2 * Gg.v2 * point) -> (point * Gg.v2 * Gg.v2 * point) -> t -> t + = fun (p0, ctrl0, ctrl1, p1) (p0', ctrl0', ctrl1', p1') t -> + + let p0 = Path.Point.get_coord p0 + and p1 = Path.Point.get_coord p1 + and p0' = Path.Point.get_coord p0' + and p1' = Path.Point.get_coord p1' + in + + let path = + Layer.move_to p1 t.path + |> Layer.line_to p1' + + (* Backward *) + |> Layer.quadratic_to + ctrl1' + ctrl0' + p0' + |> Layer.line_to p0 + + (* Forward *) + |> Layer.quadratic_to + ctrl0 + ctrl1 + p1 + |> Layer.close + |> t.close in + + + { t with path } + + let stop + : t -> t + = fun t -> + t + + let get + : t -> Layer.t + = fun t -> + t.path +end diff --git a/layer/fillEngine.mli b/layer/fillEngine.mli new file mode 100755 index 0000000..e1660f4 --- /dev/null +++ b/layer/fillEngine.mli @@ -0,0 +1,2 @@ +module Make(R:Repr.PRINTER): + Repr.ENGINE with type repr = R.t diff --git a/layer/fillPrinter.ml b/layer/fillPrinter.ml deleted file mode 100755 index f3717c2..0000000 --- a/layer/fillPrinter.ml +++ /dev/null @@ -1,89 +0,0 @@ -module Make(Repr: Repr.PRINTER) = struct - - type point = Path.Point.t - - type repr = Repr.t - - type t = - { path: Repr.t - ; close : Repr.t -> Repr.t - } - - let create_path - : (Repr.t -> Repr.t) -> t - = fun f -> - { close = f - ; path = Repr.create () - } - - (* Start a new path. *) - - let start - : point -> point -> t -> t - = fun p1 _ {close ; path } -> - let path = Repr.move_to (Path.Point.get_coord p1) path in - { close - ; path - } - - let line_to - : (point * point) -> (point * point) -> t -> t - = fun (p0, p1) (p0', p1') t -> - - let p0 = Path.Point.get_coord p0 - and p1 = Path.Point.get_coord p1 - and p0' = Path.Point.get_coord p0' - and p1' = Path.Point.get_coord p1' in - - let path = - Repr.move_to p1 t.path - |> Repr.line_to p1' - |> Repr.line_to p0' - |> Repr.line_to p0 - |> Repr.line_to p1 - |> Repr.close in - let path = t.close path in - { t with path} - - let quadratic_to - : (point * Gg.v2 * Gg.v2 * point) -> (point * Gg.v2 * Gg.v2 * point) -> t -> t - = fun (p0, ctrl0, ctrl1, p1) (p0', ctrl0', ctrl1', p1') t -> - - let p0 = Path.Point.get_coord p0 - and p1 = Path.Point.get_coord p1 - and p0' = Path.Point.get_coord p0' - and p1' = Path.Point.get_coord p1' - in - - let path = - Repr.move_to p1 t.path - |> Repr.line_to p1' - - (* Backward *) - |> Repr.quadratic_to - ctrl1' - ctrl0' - p0' - |> Repr.line_to p0 - - (* Forward *) - |> Repr.quadratic_to - ctrl0 - ctrl1 - p1 - |> Repr.close - |> t.close in - - - { t with path } - - let stop - : t -> t - = fun t -> - t - - let get - : t -> Repr.t - = fun t -> - t.path -end diff --git a/layer/fillPrinter.mli b/layer/fillPrinter.mli deleted file mode 100755 index cdcaa7c..0000000 --- a/layer/fillPrinter.mli +++ /dev/null @@ -1,2 +0,0 @@ -module Make(R:Repr.PRINTER): - Repr.LAYER with type repr = R.t diff --git a/layer/lineEngine.ml b/layer/lineEngine.ml new file mode 100755 index 0000000..3d15d9c --- /dev/null +++ b/layer/lineEngine.ml @@ -0,0 +1,68 @@ +module Make(Layer: Repr.PRINTER) = struct + + type point = Path.Point.t + + let mark point path = + let open Gg.V2 in + let point = Path.Point.get_coord point in + + let dist = 5. + and dist' = -5. in + + let path = Layer.move_to (point - (of_tuple (dist, dist))) path + |> Layer.line_to ( point + (of_tuple (dist, dist))) + |> Layer.move_to (point + (of_tuple (dist', dist))) + |> Layer.line_to ( point + (of_tuple (dist, dist'))) + in + path + + + type t = + { path: (Layer.t) + } + + type repr = Layer.t + + let create_path + : 'b -> t + = fun _ -> + { path = Layer.create () + } + + let start + : point -> point -> t -> t + = fun p1 _ { path } -> + let path = mark p1 path in + { path + } + + let line_to + : (point * point) -> (point * point) -> t -> t + = fun (p0, p1) _ {path} -> + let path = Layer.move_to (Path.Point.get_coord p0) path + |> Layer.line_to (Path.Point.get_coord p1) + |> mark p1 in + { path + } + + let quadratic_to + : (point * Gg.v2 * Gg.v2 * point) -> (point * Gg.v2 * Gg.v2 * point) -> t -> t + = fun (p0, ctrl0, ctrl1, p1) _ {path} -> + + let path = Layer.move_to (Path.Point.get_coord p0) path + |> Layer.quadratic_to ctrl0 ctrl1 (Path.Point.get_coord p1) + |> mark p1 in + + { path = path } + + let stop + : t -> t + = fun path -> path + + + let get + : t -> Layer.t + = fun {path; _} -> + path + +end diff --git a/layer/lineEngine.mli b/layer/lineEngine.mli new file mode 100755 index 0000000..86ef5fb --- /dev/null +++ b/layer/lineEngine.mli @@ -0,0 +1,2 @@ +module Make(R:Repr.PRINTER): + Repr.ENGINE with type repr = R.t diff --git a/layer/linePrinter.ml b/layer/linePrinter.ml deleted file mode 100755 index d223760..0000000 --- a/layer/linePrinter.ml +++ /dev/null @@ -1,68 +0,0 @@ -module Make(Repr: Repr.PRINTER) = struct - - type point = Path.Point.t - - let mark point path = - let open Gg.V2 in - let point = Path.Point.get_coord point in - - let dist = 5. - and dist' = -5. in - - let path = Repr.move_to (point - (of_tuple (dist, dist))) path - |> Repr.line_to ( point + (of_tuple (dist, dist))) - |> Repr.move_to (point + (of_tuple (dist', dist))) - |> Repr.line_to ( point + (of_tuple (dist, dist'))) - in - path - - - type t = - { path: (Repr.t) - } - - type repr = Repr.t - - let create_path - : 'b -> t - = fun _ -> - { path = Repr.create () - } - - let start - : point -> point -> t -> t - = fun p1 _ { path } -> - let path = mark p1 path in - { path - } - - let line_to - : (point * point) -> (point * point) -> t -> t - = fun (p0, p1) _ {path} -> - let path = Repr.move_to (Path.Point.get_coord p0) path - |> Repr.line_to (Path.Point.get_coord p1) - |> mark p1 in - { path - } - - let quadratic_to - : (point * Gg.v2 * Gg.v2 * point) -> (point * Gg.v2 * Gg.v2 * point) -> t -> t - = fun (p0, ctrl0, ctrl1, p1) _ {path} -> - - let path = Repr.move_to (Path.Point.get_coord p0) path - |> Repr.quadratic_to ctrl0 ctrl1 (Path.Point.get_coord p1) - |> mark p1 in - - { path = path } - - let stop - : t -> t - = fun path -> path - - - let get - : t -> Repr.t - = fun {path; _} -> - path - -end diff --git a/layer/linePrinter.mli b/layer/linePrinter.mli deleted file mode 100755 index 191830a..0000000 --- a/layer/linePrinter.mli +++ /dev/null @@ -1,2 +0,0 @@ -module Make(R:Repr.PRINTER): - Repr.LAYER with type repr = R.t diff --git a/layer/paths.ml b/layer/paths.ml index 6d0157e..3a8bfe8 100755 --- a/layer/paths.ml +++ b/layer/paths.ml @@ -1,12 +1,16 @@ open StdLabels (** Common module for ensuring that the function is evaluated only once *) -module type REPRESENTABLE = sig +(** This represent a single path, which can be transformed throug a [repr] + function. *) +module type PATH = sig type t (** Represent the path *) val repr - : t -> (module Path.Repr.M with type point = Path.Point.t and type t = 's) -> 's -> 's + : t -> (module Path.Repr.M + with type point = Path.Point.t + and type t = 's) -> 's -> 's end type printer = @@ -28,7 +32,10 @@ module type P = sig end -module MakePrinter(M:Repr.LAYER) : P with type point = M.point and type t = M.t and type repr = M.repr = struct +module MakePrinter(M:Repr.ENGINE) : P + with type point = M.point + and type t = M.t + and type repr = M.repr = struct type t = M.t @@ -76,22 +83,76 @@ module MakePrinter(M:Repr.LAYER) : P with type point = M.point and type t = M.t let stop = M.stop end +(** Transform the two path, into a single one. *) +module ReprSingle(T:PATH) = struct + + type t = T.t * T.t + + module R = struct + type point = Path.Point.t + + type repr' = + | Move of (point) + | Line_to of (point * point) + | Quadratic of (point * Gg.v2 * Gg.v2 * point) + + type t = repr' list + + let start t actions = + (Move t)::actions + + let line_to p0 p1 actions = + Line_to (p0, p1)::actions + + let quadratic_to + : (point * Gg.v2 * Gg.v2 * point) -> t -> t + = fun q actions -> + (Quadratic q)::actions + + let stop + : t -> t + = fun v -> List.rev v + + end + + let repr + : t -> (module Path.Repr.M with type point = Path.Point.t and type t = 's) -> 's -> 's + = fun (type s) (path, _) (module Repr:Path.Repr.M with type point = Path.Point.t and type t = s) state -> + let elems = T.repr path (module R) [] in + + let state = List.fold_left elems + ~init:state + ~f:(fun state -> function + | R.Move pt -> Repr.start pt state + | R.Line_to (p0, p1) -> Repr.line_to p0 p1 state + | R.Quadratic t -> Repr.quadratic_to t state + ) + in Repr.stop state +end + +module ReprFixed = ReprSingle(Path.Fixed) +module ReprBuild = ReprSingle(Path.Path_Builder) + (* Canva representation *) -module FillCanvaRepr = MakePrinter(FillPrinter.Make(CanvaPrinter)) -module DuctusCanvaRepr = MakePrinter(DuctusPrinter.Make(CanvaPrinter)) -module LineCanvaRepr = MakePrinter(LinePrinter.Make(CanvaPrinter)) +module FillCanva = FillEngine.Make(CanvaPrinter) +module LineCanva = LineEngine.Make(CanvaPrinter) +module DuctusCanva = FillEngine.Make(CanvaPrinter) + +module FillCanvaRepr = MakePrinter(FillCanva) +module DuctusCanvaRepr = MakePrinter(DuctusCanva) +module LineCanvaRepr = MakePrinter(LineCanva) (* SVG representation *) -module FillSVGRepr = MakePrinter(FillPrinter.Make(Svg)) -module DuctusSVGRepr = MakePrinter(DuctusPrinter.Make(Svg)) +module FillSVGRepr = MakePrinter(FillEngine.Make(Svg)) +module DuctusSVGRepr = MakePrinter(DuctusEngine.Make(Svg)) (** Draw a path to a canva *) let to_canva - : (module REPRESENTABLE with type t = 's) -> 's -> Brr_canvas.C2d.t -> printer -> unit - = fun (type s) (module R:REPRESENTABLE with type t = s) path ctx -> function + : (module PATH with type t = 's) -> 's -> Brr_canvas.C2d.t -> printer -> unit + = fun (type s) (module R:PATH with type t = s) path ctx -> function | `Fill -> R.repr path @@ -117,8 +178,8 @@ let to_canva (** Draw a path and represent it as SVG *) let to_svg - : (module REPRESENTABLE with type t = 's) -> color:Jstr.t -> 's -> printer -> Brr.El.t - = fun (type s) (module R:REPRESENTABLE with type t = s) ~color path -> function + : (module PATH with type t = 's) -> color:Jstr.t -> 's -> printer -> Brr.El.t + = fun (type s) (module R:PATH with type t = s) ~color path -> function | `Fill -> (* In order to deal with over crossing path, I cut the path in as @@ -158,49 +219,3 @@ let to_svg | `Line -> raise Not_found -(** Transform the two fixed path, into a single one. *) -module ReprFixed = struct - - type t = Path.Fixed.t * Path.Fixed.t - - module R = struct - type point = Path.Point.t - - type repr' = - | Move of (point) - | Line_to of (point * point) - | Quadratic of (point * Gg.v2 * Gg.v2 * point) - - type t = repr' list - - let start t actions = - (Move t)::actions - - let line_to p0 p1 actions = - Line_to (p0, p1)::actions - - let quadratic_to - : (point * Gg.v2 * Gg.v2 * point) -> t -> t - = fun q actions -> - (Quadratic q)::actions - - let stop - : t -> t - = fun v -> List.rev v - - end - - let repr - : t -> (module Path.Repr.M with type point = Path.Point.t and type t = 's) -> 's -> 's - = fun (type s) (path, _) (module Repr:Path.Repr.M with type point = Path.Point.t and type t = s) state -> - let elems = Path.Fixed.repr path (module R) [] in - - let state = List.fold_left elems - ~init:state - ~f:(fun state -> function - | R.Move pt -> Repr.start pt state - | R.Line_to (p0, p1) -> Repr.line_to p0 p1 state - | R.Quadratic t -> Repr.quadratic_to t state - ) - in Repr.stop state -end diff --git a/layer/repr.ml b/layer/repr.ml index 85b0f3b..552e2b7 100755 --- a/layer/repr.ml +++ b/layer/repr.ml @@ -18,7 +18,7 @@ module type PRINTER = sig end -module type LAYER = sig +module type ENGINE = sig type t type point = Path.Point.t diff --git a/path/fixed.ml b/path/fixed.ml index 2eda3c1..d61bb0a 100755 --- a/path/fixed.ml +++ b/path/fixed.ml @@ -37,14 +37,7 @@ module Make(Point:P) = struct ; move : path } - type t = - { id: int - ; path : step array - } - - let id - : t -> int - = fun {id; _} -> id + type t = step array module ToFixed = struct type point = Point.t @@ -93,20 +86,15 @@ module Make(Point:P) = struct res end - let internal_id = ref 0 - let to_fixed : (module BUILDER with type t = 'a) -> 'a -> t = fun (type s) (module Builder: BUILDER with type t = s) t -> - incr internal_id; - { id = !internal_id - ; path = Builder.repr t (module ToFixed) (ToFixed.create_path ()) - |> ToFixed.get - } + Builder.repr t (module ToFixed) (ToFixed.create_path ()) + |> ToFixed.get let repr : t -> (module Repr.M with type point = Point.t and type t = 's) -> 's -> 's - = fun (type s) {path; _} (module Repr : Repr.M with type point = Point.t and type t = s) repr -> + = fun (type s) t (module Repr : Repr.M with type point = Point.t and type t = s) repr -> let repr_bezier p p0 bezier = Repr.quadratic_to ( p0 @@ -115,7 +103,7 @@ module Make(Point:P) = struct , bezier.p1 ) p in - let _, repr = Array.fold_left path + let _, repr = Array.fold_left t ~init:(true, repr) ~f:(fun (first, path) element -> let path = if first then @@ -143,9 +131,9 @@ module Make(Point:P) = struct None if the point is out of the curve *) let distance : Gg.v2 -> t -> approx option - = fun point path -> + = fun point t -> - Array.fold_left path.path + Array.fold_left t ~init:None ~f:(fun res step -> match step.move with @@ -180,25 +168,24 @@ module Make(Point:P) = struct let map : t -> (Point.t -> Point.t) -> t - = fun {id; path} f -> - let path = Array.map path - ~f:(fun step -> - match step.move with - | Line p2 -> - { point = f step.point - ; move = Line (f p2) - } - | Curve bezier -> - let point = f step.point in - { point - ; move = Curve {bezier with p1 = f bezier.p1} } - ) in - {id; path} + = fun t f -> + Array.map t + ~f:(fun step -> + match step.move with + | Line p2 -> + { point = f step.point + ; move = Line (f p2) + } + | Curve bezier -> + let point = f step.point in + { point + ; move = Curve {bezier with p1 = f bezier.p1} } + ) let iter : t -> f:(Point.t -> unit) -> unit - = fun {path; _} ~f -> - Array.iter path + = fun t ~f -> + Array.iter t ~f:(fun step -> match step.move with | Line p2 -> f step.point; f p2 @@ -230,7 +217,7 @@ module Make(Point:P) = struct } - let build_from_three_points id p0 p1 p2 = + let build_from_three_points p0 p1 p2 = let bezier = Shapes.Bezier.quadratic_to_cubic @@ Shapes.Bezier.three_points_quadratic @@ -249,52 +236,48 @@ module Make(Point:P) = struct and p1' = Point.copy p1 b0.Shapes.Bezier.p1 and p2' = Point.copy p2 b1.Shapes.Bezier.p1 in - { id - ; path = - [| { point = p0' - ; move = - Curve { ctrl0 = b0.Shapes.Bezier.ctrl0 - ; ctrl1 = b0.Shapes.Bezier.ctrl1 - ; p1 = p1' - } } - ; { point = p1' - ; move = Curve { ctrl0 = b1.Shapes.Bezier.ctrl0 - ; ctrl1 = b1.Shapes.Bezier.ctrl1 - ; p1 = p2' } - } |] - } + [| { point = p0' + ; move = + Curve { ctrl0 = b0.Shapes.Bezier.ctrl0 + ; ctrl1 = b0.Shapes.Bezier.ctrl1 + ; p1 = p1' + } } + ; { point = p1' + ; move = Curve { ctrl0 = b1.Shapes.Bezier.ctrl0 + ; ctrl1 = b1.Shapes.Bezier.ctrl1 + ; p1 = p2' } + } |] (** Rebuild the whole curve by evaluating all the points *) let rebuild : t -> t option - = fun {id ; path} -> + = fun t -> - match Array.length path with + match Array.length t with | 0 -> None | 1 -> - let step = Array.get path 0 in + let step = Array.get t 0 in begin match step.move with | Curve {p1; _} | Line p1 -> Some - { id - ; path= [| - { point = step.point - ; move = Line p1 } |]} + [| + { point = step.point + ; move = Line p1 } |] end | 2 -> - let p0 = (Array.get path 0).point - and p1 = (Array.get path 1).point - and p2 = get_point' @@ Array.get path 1 in - Some (build_from_three_points id p0 p1 p2) + let p0 = (Array.get t 0).point + and p1 = (Array.get t 1).point + and p2 = get_point' @@ Array.get t 1 in + Some (build_from_three_points p0 p1 p2) | _ -> (* Convert all the points in list *) let points = List.init - ~len:((Array.length path) ) - ~f:(fun i -> Point.get_coord @@ get_point' (Array.get path i)) in - let p0 = Point.get_coord @@ (Array.get path 0).point in + ~len:((Array.length t) ) + ~f:(fun i -> Point.get_coord @@ get_point' (Array.get t i)) in + let p0 = Point.get_coord @@ (Array.get t 0).point in let points = p0::points in @@ -305,8 +288,8 @@ module Make(Point:P) = struct (* Now for each point, reassociate the same point information, We should have as many points as before *) - let rebuilded = Array.map2 beziers path ~f:assoc_point in - Some {id; path = rebuilded} + let rebuilded = Array.map2 beziers t ~f:assoc_point in + Some rebuilded end let find_pt_index @@ -338,44 +321,43 @@ module Make(Point:P) = struct let remove_point : t -> Point.t -> t option - = fun {id; path} point -> + = fun t point -> - match Array.length path with + match Array.length t with | 0 | 1 -> None | 2 -> (* Two segment, we get the points and transform this into a single line *) - let p0 = (Array.get path 0).point - and p1 = (Array.get path 1).point - and p2 = get_point' @@ Array.get path 1 in + let p0 = (Array.get t 0).point + and p1 = (Array.get t 1).point + and p2 = get_point' @@ Array.get t 1 in let elms = List.filter [p0; p1; p2] ~f:(fun pt -> Point.id pt != Point.id point) in begin match elms with | p0::p1::[] -> Some - { id - ; path = [| { point = p0 - ; move = Line p1 }|]} + [| { point = p0 + ; move = Line p1 }|] | _ -> None end | l -> - match find_pt_index point path with - | None -> Some {id; path} + match find_pt_index point t with + | None -> Some t | Some 0 -> (* Remove the first point *) let path = Array.init (l-1) - ~f:( fun i -> Array.get path (i+1)) in - Some { id ; path } - | Some n when n = (Array.length path) -> + ~f:( fun i -> Array.get t (i+1)) in + Some path + | Some n when n = (Array.length t) -> (* Remove the last point *) let path = Array.init (l-1) - ~f:( fun i -> Array.get path i) in - Some { id ; path } + ~f:( fun i -> Array.get t i) in + Some path | Some n -> let path' = Array.init (l-1) ~f:(fun i -> if i < (n-1) then - Array.get path (i) + Array.get t (i) else if i = (n-1) then (* We know that the point is not the first nor the last one. So it is safe to call n-1 or n + 1 point @@ -383,9 +365,9 @@ module Make(Point:P) = struct We have to rebuild the point and set that point_(-1).id = point_(+1).id *) - let p0 = (Array.get path i).point in + let p0 = (Array.get t i).point in - match (Array.get path (i+1)).move with + match (Array.get t (i+1)).move with | Line p1 -> { point = p0 ; move = Line p1 } @@ -394,11 +376,9 @@ module Make(Point:P) = struct ; move = Curve c } else - Array.get path (i+1) + Array.get t (i+1) ) in - rebuild - { id - ; path=path'} + rebuild path' let first_point : step -> Point.t @@ -406,46 +386,46 @@ module Make(Point:P) = struct let replace_point : t -> Point.t -> t option - = fun {id; path } p -> + = fun t p -> let add_path paths idx f points = if 0 <= idx && idx < Array.length paths then - let path = Array.get path idx in + let path = Array.get t idx in Point.get_coord (f path) :: points else points in - match Array.length path with + match Array.length t with | 0 -> None | 1 -> (* Only one point, easy ? *) - let step = Array.get path 0 in + let step = Array.get t 0 in begin match step.move with | Curve {p1; _} | Line p1 -> let p0 = if (Point.id step.point = Point.id p) then p else step.point and p1 = if (Point.id p1 = Point.id p) then p else p1 in - Some {id; path=[| - { point = p0 - ; move = Line p1 } - |]} + Some [| + { point = p0 + ; move = Line p1 } + |] end | 2 -> - let p0 = (Array.get path 0).point - and p1 = (Array.get path 1).point - and p2 = get_point' @@ Array.get path 1 in + let p0 = (Array.get t 0).point + and p1 = (Array.get t 1).point + and p2 = get_point' @@ Array.get t 1 in let p0 = if (Point.id p0 = Point.id p) then p else p0 and p1 = if (Point.id p1 = Point.id p) then p else p1 and p2 = if (Point.id p2 = Point.id p) then p else p2 in - Some (build_from_three_points id p0 p1 p2) + Some (build_from_three_points p0 p1 p2) (* More than two segmend, it is ok for a partial reevaluation *) | _ -> - match find_pt_index p path with + match find_pt_index p t with | None -> None | Some n -> - let path = Array.copy path in + let path = Array.copy t in let p0, p1 = @@ -480,7 +460,7 @@ module Make(Point:P) = struct if (n-2 < idx) && (idx < n +2) && idx < Array.length path then Array.set path idx (assoc_point bezier (Array.get path idx)) ); - Some {id; path} + Some path | Error _ -> let bezier', _ = Shapes.Bezier.three_points_quadratic (Point.get_coord p) @@ -497,6 +477,6 @@ module Make(Point:P) = struct ; p1 }) }; - Some {id; path} + Some path end end diff --git a/path/fixed.mli b/path/fixed.mli index 862409b..111187c 100755 --- a/path/fixed.mli +++ b/path/fixed.mli @@ -22,10 +22,6 @@ module Make(Point:P) : sig type t - (** Return the identifier for this path *) - val id - : t -> int - (** Create a path from a builder *) val to_fixed : (module BUILDER with type t = 'a) -> 'a -> t diff --git a/script.it/dune b/script.it/dune index c51c43b..bb5ca5f 100755 --- a/script.it/dune +++ b/script.it/dune @@ -1,3 +1,11 @@ +(library + (name outline) + (libraries + path) + (modules outline) + (preprocess (pps ppx_hash js_of_ocaml-ppx)) + ) + (executable (name script) (libraries @@ -8,6 +16,7 @@ blog layer worker_messages + outline ) (modes js) (modules script state selection) @@ -29,6 +38,7 @@ shapes path worker_messages + outline ) (modes js) (preprocess (pps ppx_hash js_of_ocaml-ppx)) diff --git a/script.it/outline.ml b/script.it/outline.ml new file mode 100755 index 0000000..4962d8e --- /dev/null +++ b/script.it/outline.ml @@ -0,0 +1,12 @@ +let internal_path_id = ref 0 + +type t = + { id : int + ; path: Path.Fixed.t + ; back: Path.Fixed.t + } + +let get_id = + let id = !internal_path_id in + incr internal_path_id; + id diff --git a/script.it/script.ml b/script.it/script.ml index 05bec1b..9ef15fe 100755 --- a/script.it/script.ml +++ b/script.it/script.ml @@ -225,7 +225,7 @@ let on_change canva mouse_position timer state = in - Layer.Paths.to_canva (module Path.Path_Builder) current context state.rendering; + Layer.Paths.to_canva (module Layer.Paths.ReprBuild) (current, current) context state.rendering; List.iter state.paths ~f:(fun path -> @@ -233,7 +233,7 @@ let on_change canva mouse_position timer state = let () = match state.mode with | Selection (Path id) | Selection (Point (id, _)) -> - begin match id = (Path.Fixed.id path) with + begin match id = path.Outline.id with | true -> (* If the element is the selected one, change the color *) Cd2d.set_fill_style context (Cd2d.color Blog.Nord.nord8); @@ -245,7 +245,8 @@ let on_change canva mouse_position timer state = | _ -> () in - Layer.Paths.to_canva (module Layer.Paths.ReprFixed) (path, path) context state.rendering + let p = path.Outline.path in + Layer.Paths.to_canva (module Layer.Paths.ReprFixed) (p, p) context state.rendering ); let () = match state.mode with @@ -254,8 +255,9 @@ let on_change canva mouse_position timer state = List.iter state.paths ~f:(fun path -> - if id = Path.Fixed.id path then - Layer.Paths.to_canva (module Layer.Paths.ReprFixed) (path, path) context `Line + if id = path.Outline.id then + let p = path.Outline.path in + Layer.Paths.to_canva (module Layer.Paths.ReprFixed) (p, p) context `Line ) | Selection (Point (id, point)) -> (* As before, mark the selected path *) @@ -264,20 +266,20 @@ let on_change canva mouse_position timer state = List.iter state.paths ~f:(fun path -> - if id = Path.Fixed.id path then + if id = path.Outline.id then let path = begin match pos with | Some pos -> let pos_v2 = Gg.V2.of_tuple pos in if Elements.Timer.delay timer < 0.3 then - path + path.Outline.path else let point' = Path.Point.copy point pos_v2 in - begin match Path.Fixed.replace_point path point' with - | None -> path + begin match Path.Fixed.replace_point path.Outline.path point' with + | None -> path.Outline.path | Some p -> p end - | None -> path end in + | None -> path.Outline.path end in Layer.Paths.to_canva (module Layer.Paths.ReprFixed) (path, path) context `Line ); diff --git a/script.it/selection.ml b/script.it/selection.ml index 591ea38..d00f026 100755 --- a/script.it/selection.ml +++ b/script.it/selection.ml @@ -7,32 +7,32 @@ type t = let threshold = 20. let get_from_paths - : (float * float) -> Path.Fixed.t list -> float * (Gg.v2 * Path.Fixed.t * Path.Point.t * Path.Point.t) option - = fun position paths -> + : (float * float) -> Outline.t list -> float * (Gg.v2 * Outline.t * Path.Point.t * Path.Point.t) option + = fun position outlines -> let point = Gg.V2.of_tuple position in (* If the user click on a curve, select it *) - List.fold_left paths + List.fold_left outlines ~init:(threshold, None) - ~f:(fun (dist, selection) path -> - match Path.Fixed.distance point path with + ~f:(fun (dist, selection) outline -> + match Path.Fixed.distance point outline.Outline.path with | Some { closest_point ; distance; p0; p1 ; ratio} when distance < dist -> - ratio, Some (closest_point, path, p0, p1) + ratio, Some (closest_point, outline, p0, p1) | _ -> dist, selection ) let select_path - : Path.Fixed.t -> t - = fun path -> Path (Path.Fixed.id path) + : Outline.t -> t + = fun outline -> Path outline.Outline.id let select_point - : Path.Fixed.t -> Gg.v2 -> t - = fun path v2_point -> + : Outline.t -> Gg.v2 -> t + = fun outline v2_point -> let point' = ref None in let dist = ref threshold in Path.Fixed.iter - path + outline.Outline.path ~f:(fun p -> let open Gg.V2 in let new_dist = norm ((Path.Point.get_coord p) - v2_point) in @@ -45,9 +45,9 @@ let select_point match !point' with | Some point -> - Point (Path.Fixed.id path, point) + Point (outline.Outline.id, point) | None -> - Path (Path.Fixed.id path) + Path (outline.Outline.id) (* (* If the point does not exists, find the exact point on the curve *) diff --git a/script.it/selection.mli b/script.it/selection.mli index a405edc..984eae6 100755 --- a/script.it/selection.mli +++ b/script.it/selection.mli @@ -14,10 +14,10 @@ val threshold : float *) val get_from_paths - : (float * float) -> Path.Fixed.t list -> float * (Gg.v2 * Path.Fixed.t * Path.Point.t * Path.Point.t) option + : (float * float) -> Outline.t list -> float * (Gg.v2 * Outline.t * Path.Point.t * Path.Point.t) option val select_path - : Path.Fixed.t -> t + : Outline.t -> t val select_point - : Path.Fixed.t -> Gg.v2 -> t + : Outline.t -> Gg.v2 -> t diff --git a/script.it/state.ml b/script.it/state.ml index c147c2c..403efbe 100755 --- a/script.it/state.ml +++ b/script.it/state.ml @@ -23,7 +23,7 @@ type render_event = type worker_event = [ `Basic of Jv.t - | `Complete of Path.Fixed.t + | `Complete of Outline.t ] type events = @@ -42,7 +42,7 @@ type events = *) type state = { mode : mode - ; paths : Path.Fixed.t list + ; paths : Outline.t list ; current : Path.Path_Builder.t ; width : float ; angle : float @@ -78,29 +78,31 @@ let insert_or_replace state ((x, y) as p) stamp path = (** Update the path in the selection with the given function applied to every point *) -let update_path_selection id paths f = - List.map paths - ~f:(fun path -> - let id' = Path.Fixed.id path in - match id = id' with - | false -> path - | true -> Path.Fixed.map path f - ) +let update_path_selection + : int -> Outline.t list -> (Path.Point.t -> Path.Point.t) -> Outline.t list + = fun id outlines f -> + List.map outlines + ~f:(fun outline -> + let id' = outline.Outline.id in + match id = id' with + | false -> outline + | true -> {outline with path = Path.Fixed.map outline.path f} + ) let update_point_selection state path_id point f = let paths = List.map state.paths ~f:(fun p -> - match Path.Fixed.id p = path_id with + match p.Outline.id = path_id with | false -> p | true -> - Path.Fixed.map - p - (fun p -> - if (Path.Point.id p = Path.Point.id point) then - f p - else - p - ) + { p with path = Path.Fixed.map + p.path + (fun p -> + if (Path.Point.id p = Path.Point.id point) then + f p + else + p + ) } ) in { state with paths } @@ -129,7 +131,7 @@ let delete state worker = let paths = List.filter state.paths ~f:(fun p -> - Path.Fixed.id p != id + p.Outline.id != id ) in { state with paths ; mode = Out} @@ -137,7 +139,7 @@ let delete state worker = List.iter state.paths ~f:(fun p -> - let id' = Path.Fixed.id p in + let id' = p.Outline.id in match id' = id with | false -> () | true -> @@ -165,30 +167,46 @@ let tick (delay, point) state = { state with current } | _ -> state -let angle angle state = +let angle worker angle state = match state.mode with (* Change angle for the whole path *) | Selection (Path s) -> let state = { state with angle } in let paths = update_path_selection s state.paths (fun p -> Path.Point.set_angle p angle) in + (* Update the event to the worker *) + let outline = List.find paths + ~f:(fun o -> o.Outline.id = s) in + post worker (`Back outline); {state with paths } (* Change angle localy *) | Selection (Point (s, point)) -> let state = update_point_selection state s point (fun p -> Path.Point.set_angle p angle) in + (* Update the event to the worker *) + let outline = List.find state.paths + ~f:(fun o -> o.Outline.id = s) in + post worker (`Back outline); { state with angle } | _ -> { state with angle} -let width width state = +let width worker width state = match state.mode with | Selection (Path s) -> let state = { state with width } in let paths = update_path_selection s state.paths (fun p -> Path.Point.set_width p width) in + (* Update the event to the worker *) + let outline = List.find paths + ~f:(fun o -> o.Outline.id = s) in + post worker (`Back outline); {state with paths } | Selection (Point (s, point)) -> let state = update_point_selection state s point (fun p -> Path.Point.set_width p width) in + (* Update the event to the worker *) + let outline = List.find state.paths + ~f:(fun o -> o.Outline.id = s) in + post worker (`Back outline); { state with width } | _ -> { state with width } @@ -234,12 +252,12 @@ let do_action { state with mode = Out } | dist, Some selection -> - let _, path, _, _ = selection in - if Path.Fixed.id path != id then + let _, outline, _, _ = selection in + if outline.Outline.id != id then select_segment position selection state dist else (* On the same segment, check for a point *) - let selection = Selection.select_point path (Gg.V2.of_tuple position) in + let selection = Selection.select_point outline (Gg.V2.of_tuple position) in match selection with | Path _ -> { state with mode = Selection selection } @@ -263,9 +281,21 @@ let do_action let current = insert_or_replace state point stamp state.current in let paths = - let last = Path.Fixed.to_fixed + + let path = Path.Fixed.to_fixed (module Path.Path_Builder) - current + current in + + (* Create a copy from the path with all the interior points *) + let back = Path.Fixed.map + path + (fun pt -> Path.Point.copy pt @@ Path.Point.get_coord' pt) in + + let last = + { Outline.path = path + ; Outline.back = back + ; Outline.id = Outline.get_id + } in let () = post worker (`Complete last) in @@ -298,14 +328,17 @@ let do_action else let point' = Path.Point.copy point mouse_v2 in List.iter state.paths - ~f:(fun path -> - let id' = Path.Fixed.id path in + ~f:(fun outline -> + let id' = outline.Outline.id in match id = id' with | false -> () | true -> Option.iter - (fun p -> post worker (`Complete p)) - (Path.Fixed.replace_point path point') + (fun p -> + + let outline = {outline with path = p} in + post worker (`Complete outline)) + (Path.Fixed.replace_point outline.Outline.path point') ); { state with mode = Selection (Point (id, point')) } | `Delete, _ -> @@ -325,7 +358,7 @@ let do_action Layer.Paths.to_svg ~color:Blog.Nord.nord0 (module Layer.Paths.ReprFixed) - (path, path) + (path.Outline.path, path.Outline.path) state.rendering )) in @@ -347,9 +380,9 @@ let do_action state | `Angle value , _ -> - angle value state + angle worker value state | `Width value, _ -> - width value state + width worker value state | `Rendering rendering, _ -> @@ -361,14 +394,13 @@ let do_action state | `Complete path, _ -> - let id = Path.Fixed.id path in + let id = path.Outline.id in let paths = List.map state.paths - ~f:(fun path' -> - let id' = Path.Fixed.id path' in + ~f:(fun line -> + let id' = line.Outline.id in match id = id' with - | false -> path' - | true -> - path + | false -> line + | true -> path ) in { state with paths } diff --git a/script.it/worker.ml b/script.it/worker.ml index 00e4595..898df39 100755 --- a/script.it/worker.ml +++ b/script.it/worker.ml @@ -1,21 +1,43 @@ open Js_of_ocaml +let (let=?) : 'a option -> ('a -> unit) -> unit + = fun f opt -> Option.iter opt f + let post_message : Worker_messages.from_worker -> unit = Worker.post_message let execute (command: [> Worker_messages.to_worker]) = match command with - | `Complete path -> - begin match Path.Fixed.rebuild path with - | Some path -> Worker.post_message (`Complete path) - | None -> () - end - | `DeletePoint (point, path) -> - begin match Path.Fixed.remove_point path point with - | Some path -> Worker.post_message (`Complete path) - | None -> () - end + + (* Full rebuild, evaluate the whole path *) + | `Complete outline -> + let path = outline.Outline.path in + + let=? path = Path.Fixed.rebuild path in + let back = Path.Fixed.map + path + (fun pt -> Path.Point.copy pt @@ Path.Point.get_coord' pt) in + let=? back = Path.Fixed.rebuild back in + Worker.post_message (`Complete {outline with path; back}) + + (* Remove the point from the main line, and reevaluate the whole path *) + | `DeletePoint (point, outline) -> + let=? path = Path.Fixed.remove_point outline.Outline.path point in + let back = Path.Fixed.map + path + (fun pt -> Path.Point.copy pt @@ Path.Point.get_coord' pt) in + let=? back = Path.Fixed.rebuild back in + Worker.post_message (`Complete {outline with path; back}) + + (* Only evaluate the interior *) + | `Back outline -> + let back = Path.Fixed.map + outline.Outline.path + (fun pt -> Path.Point.copy pt @@ Path.Point.get_coord' pt) in + let=? back = Path.Fixed.rebuild back in + Worker.post_message (`Complete {outline with back}) + | _ -> post_message (`Other (Js.string "Unknown message received")) diff --git a/script.it/worker_messages/dune b/script.it/worker_messages/dune index d1511a6..b4e1c2b 100755 --- a/script.it/worker_messages/dune +++ b/script.it/worker_messages/dune @@ -2,5 +2,6 @@ (name worker_messages) (libraries js_of_ocaml + outline path) ) diff --git a/script.it/worker_messages/worker_messages.ml b/script.it/worker_messages/worker_messages.ml index 992ec29..a4d05c8 100755 --- a/script.it/worker_messages/worker_messages.ml +++ b/script.it/worker_messages/worker_messages.ml @@ -1,8 +1,9 @@ open Js_of_ocaml type to_worker = [ - | `Complete of Path.Fixed.t - | `DeletePoint of (Path.Point.t * Path.Fixed.t) + | `Complete of Outline.t + | `DeletePoint of (Path.Point.t * Outline.t) + | `Back of Outline.t ] type from_worker = [ -- cgit v1.2.3