summaryrefslogtreecommitdiff
path: root/script.it/path
diff options
context:
space:
mode:
authorSébastien Dailly <sebastien@chimrod.com>2021-02-05 09:08:39 +0100
committerSébastien Dailly <sebastien@dailly.me>2022-02-07 14:39:30 +0100
commit561d0f0155f4906d90eb7e73a3ff9cb28909126f (patch)
tree9a606c2d7832272ea33d7052512a5fa59805d582 /script.it/path
parent86ec559f913c389e8dc055b494630f21a45e039b (diff)
Update project structure
Diffstat (limited to 'script.it/path')
-rwxr-xr-xscript.it/path/builder.ml224
-rwxr-xr-xscript.it/path/builder.mli43
-rwxr-xr-xscript.it/path/dune7
-rwxr-xr-xscript.it/path/fixed.ml487
-rwxr-xr-xscript.it/path/fixed.mli81
-rwxr-xr-xscript.it/path/path.ml7
-rwxr-xr-xscript.it/path/point.ml77
-rwxr-xr-xscript.it/path/point.mli40
-rwxr-xr-xscript.it/path/repr.ml19
9 files changed, 985 insertions, 0 deletions
diff --git a/script.it/path/builder.ml b/script.it/path/builder.ml
new file mode 100755
index 0000000..4403599
--- /dev/null
+++ b/script.it/path/builder.ml
@@ -0,0 +1,224 @@
+open StdLabels
+
+(** Signature for points *)
+module type P = sig
+ type t
+
+ val empty : t
+
+ val get_coord : t -> Gg.v2
+
+ val copy : t -> Gg.v2 -> t
+
+end
+
+module Make(Point:P) = struct
+
+ (** Point creation **)
+
+ type bezier =
+ { p0:Point.t (* The starting point *)
+ ; p1:Point.t (* The end point *)
+ ; ctrl0:Gg.v2 (* The control point *)
+ ; ctrl1:Gg.v2 } (* The control point *)
+
+ type t = Point.t list * bezier list
+
+ let get_new_segment connexion0 p5 p4 p3 p2 p1 =
+ let p5' = Point.get_coord p5
+ and p4' = Point.get_coord p4
+ and p3' = Point.get_coord p3
+ and p2' = Point.get_coord p2
+ and p1' = Point.get_coord p1 in
+
+ let points_to_link =
+ [ p1'
+ ; p2'
+ ; p3'
+ ; p4'
+ ; p5' ] in
+ Shapes.Bspline.to_bezier ?connexion0 points_to_link
+
+ let empty = ([], [])
+
+ let add_point
+ : Point.t -> t -> t
+ = fun lastPoint (path, beziers) ->
+ let (let*) v f =
+ match v with
+ | Ok bezier ->
+ if Array.length bezier > 0 then
+ f (Array.get bezier 0)
+ else
+ (lastPoint::path, beziers)
+ | _ ->
+ (lastPoint::path, beziers)
+ in
+
+ let connexion0 = match beziers with
+ | hd::_ -> Some (Point.get_coord hd.p1)
+ | _ -> None in
+
+ match path with
+ | p4::p3::p2::p1::_ ->
+ let* bezier = get_new_segment connexion0
+ lastPoint p4 p3 p2 p1 in
+
+ let bezier_point =
+ { p0 = p2
+ ; p1 = p1
+ ; ctrl0 = bezier.Shapes.Bezier.ctrl1
+ ; ctrl1 = bezier.Shapes.Bezier.ctrl0
+ } in
+
+ (* We remove the last point and add the bezier curve in the list*)
+ let firsts = lastPoint::p4::p3::p2::[] in
+ (firsts, bezier_point::beziers)
+ | _ ->
+ ( lastPoint::path
+ , beziers)
+
+ let replace_last
+ : Point.t -> t -> t
+ = fun lastPoint ((path, beziers) as t) ->
+ match path, beziers with
+ | _::(tl), beziers ->
+
+ ( lastPoint::tl
+ , beziers )
+ | _ ->
+ add_point lastPoint t
+
+ let peek2
+ : t -> (Point.t * Point.t) option
+ = fun (path, _) ->
+ match path with
+ | h1::h2::_ -> Some (h1, h2)
+ | _ -> None
+
+ let peek
+ : t -> Point.t option
+ = fun (path, _) ->
+ match path with
+ | [] -> None
+ | hd::_ -> Some hd
+
+ let repr
+ : t -> (module Repr.M with type point = Point.t and type t = 's) -> 's -> 's
+ = fun (type s) (points, beziers) (module Repr : Repr.M with type point = Point.t and type t = s) path ->
+
+ (* Represent the last points *)
+ let path = match points with
+ | [] ->
+ ( path )
+ | p1::[] ->
+ ( Repr.start p1 path )
+ | p1::p2::[] ->
+ let path =
+ Repr.start p1 path
+ |> Repr.line_to p1 p2 in
+ ( path )
+ | p0::p1::p2::[] ->
+
+ let b0, b1 = Shapes.Bezier.quadratic_to_cubic
+ @@ Shapes.Bezier.three_points_quadratic
+ (Point.get_coord p0)
+ (Point.get_coord p1)
+ (Point.get_coord p2)
+ |> Shapes.Bezier.slice 0.5
+ in
+ let p0' = Point.copy p0 b0.Shapes.Bezier.p0
+ and p1' = Point.copy p1 b0.Shapes.Bezier.p1
+ and p2' = Point.copy p2 b1.Shapes.Bezier.p1 in
+
+ Repr.start p0 path
+ |> Repr.quadratic_to
+ ( p0'
+ , b0.Shapes.Bezier.ctrl0
+ , b0.Shapes.Bezier.ctrl1
+ , p1' )
+ |> Repr.quadratic_to
+ ( p1'
+ , b1.Shapes.Bezier.ctrl0
+ , b1.Shapes.Bezier.ctrl1
+ , p2' )
+ | (p0::_ as points) ->
+
+ let (let*) v f =
+ match v with
+ | Ok beziers -> f beziers
+ | _ -> path in
+
+ let points' = List.map ~f:Point.get_coord points in
+ let connexion = match beziers with
+ | [] -> None
+ | hd ::_ -> Some (Point.get_coord hd.p1) in
+
+ let* beziers = Shapes.Bspline.to_bezier ?connexion1:connexion points' in
+
+ (* Stdlib does not provide fold_left_i function and we need to map
+ each bezier point with the associated point in the curve.
+
+ So I use references here for keeping each result element
+
+ *)
+ let path = ref path in
+ let point = ref p0 in
+
+ List.iteri
+ points
+ ~f:(fun i pt ->
+
+ (* The first iteration is ignored, as we need both previous and
+ current point for the two point in the curve.
+
+ Do not forget that there is always n-1 bezier curve for n
+ points *)
+ if i > 0 then (
+
+ let bezier = Array.get beziers (i - 1) in
+
+ path := Repr.quadratic_to
+ ( !point
+ , bezier.Shapes.Bezier.ctrl0
+ , bezier.Shapes.Bezier.ctrl1
+ , pt )
+ (!path);
+ point := pt;
+ )
+ );
+ ( !path )
+ in
+
+ (* Now represent the already evaluated points. Much easer to do, just
+ iterate on them *)
+ Repr.stop @@ List.fold_left beziers
+ ~init:path
+ ~f:(fun path bezier ->
+ Repr.quadratic_to
+ ( bezier.p0
+ , bezier.ctrl0
+ , bezier.ctrl1
+ , bezier.p1 )
+ path
+ )
+
+ let map
+ : t -> (Point.t -> Point.t) -> t
+ = fun (points, beziers) f ->
+ let points = List.map
+ points
+ ~f
+ and beziers = List.map
+ beziers
+ ~f:(fun bezier ->
+
+ { p0 = f bezier.p0
+ ; p1 = f bezier.p1
+ ; ctrl0 = Point.(get_coord (f ( copy bezier.p0 bezier.ctrl0)))
+ ; ctrl1 = Point.(get_coord (f ( copy bezier.p1 bezier.ctrl1)))
+ }
+ ) in
+ points, beziers
+
+end
diff --git a/script.it/path/builder.mli b/script.it/path/builder.mli
new file mode 100755
index 0000000..2afbd4b
--- /dev/null
+++ b/script.it/path/builder.mli
@@ -0,0 +1,43 @@
+(** Signature for points *)
+module type P = sig
+ type t
+
+ val empty : t
+
+ val get_coord : t -> Gg.v2
+
+ (** Copy a point and all thoses properties to the given location *)
+ val copy : t -> Gg.v2 -> t
+
+end
+
+module Make(Point:P) : sig
+
+ type t
+
+ (** Create an empty path *)
+ val empty: t
+
+ val add_point
+ : Point.t -> t -> t
+
+ (** Replace the last alement in the path by the one given in parameter *)
+ val replace_last
+ : Point.t -> t -> t
+
+ (** Retrieve the last element, if any *)
+ val peek
+ : t -> Point.t option
+
+ (** Retrieve the last element, if any *)
+ val peek2
+ : t -> (Point.t * Point.t) option
+
+ (** Represent the path *)
+ val repr
+ : t -> (module Repr.M with type point = Point.t and type t = 's) -> 's -> 's
+
+ val map
+ : t -> (Point.t -> Point.t) -> t
+
+end
diff --git a/script.it/path/dune b/script.it/path/dune
new file mode 100755
index 0000000..863c768
--- /dev/null
+++ b/script.it/path/dune
@@ -0,0 +1,7 @@
+(library
+ (name path)
+ (libraries
+ gg
+ shapes
+ )
+ )
diff --git a/script.it/path/fixed.ml b/script.it/path/fixed.ml
new file mode 100755
index 0000000..1362ad3
--- /dev/null
+++ b/script.it/path/fixed.ml
@@ -0,0 +1,487 @@
+open StdLabels
+
+(** Signature for points *)
+module type P = sig
+ type t
+
+ val get_coord : t -> Gg.v2
+
+ val id : t -> int
+
+ val copy : t -> Gg.v2 -> t
+
+end
+
+module Make(Point:P) = struct
+
+ type bezier =
+ { ctrl0:Gg.v2 (* The control point *)
+ ; ctrl1:Gg.v2 (* The control point *)
+ ; p1:Point.t (* The end point *)
+ }
+
+ module type BUILDER = sig
+ type t
+
+ val repr
+ : t -> (module Repr.M with type point = Point.t and type t = 's) -> 's -> 's
+ end
+
+ type path =
+ | Line of Point.t
+ | Curve of bezier
+
+
+ type step =
+ { point : Point.t
+ ; move : path
+ }
+
+ type t = step array
+
+ module ToFixed = struct
+ type point = Point.t
+
+ type t = int * step list
+
+ let create_path () = 0, []
+
+ (* Start a new path. *)
+ let start point t =
+ let _ = point in
+ t
+
+ let line_to
+ : point -> point -> t -> t
+ = fun p1 p2 (i, t) ->
+ ( i + 1
+ , { point = p1
+ ; move = Line p2
+ }:: t )
+
+ let quadratic_to
+ : (point * Gg.v2 * Gg.v2 * point) -> t -> t
+ = fun (p0, ctrl0, ctrl1, p1) (i, t) ->
+ let curve = Curve
+ { ctrl0
+ ; ctrl1
+ ; p1} in
+ ( i + 1
+ , { point = p0
+ ; move = curve
+ } ::t)
+
+ let stop t = t
+
+ let get
+ : int * step list -> step array
+ = fun (n, t) ->
+
+ (* The array is initialized with a magic number, and just after
+ filled with the values from the list in reverse. All the elements are set.
+ *)
+ let res = Obj.magic (Array.make n 0) in
+ List.iteri t
+ ~f:(fun i elem -> Array.set res (n - i - 1) elem );
+ res
+ end
+
+ let to_fixed
+ : (module BUILDER with type t = 'a) -> 'a -> t
+ = fun (type s) (module Builder: BUILDER with type t = s) t ->
+ 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) 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
+ , bezier.ctrl0
+ , bezier.ctrl1
+ , bezier.p1 )
+ p in
+
+ let _, repr = Array.fold_left t
+ ~init:(true, repr)
+ ~f:(fun (first, path) element ->
+ let path = if first then
+ Repr.start element.point path
+ else path in
+ match element.move with
+ | Line p1 ->
+ ( false
+ , Repr.line_to element.point p1 path )
+ | Curve bezier ->
+ ( false
+ , repr_bezier path element.point bezier )
+ ) in
+ Repr.stop repr
+
+
+ type approx =
+ { distance : float
+ ; closest_point : Gg.v2
+ ; ratio : float
+ ; p0 : Point.t
+ ; p1 : Point.t }
+
+ (** Return the distance between a given point and the curve. May return
+ None if the point is out of the curve *)
+ let distance
+ : Gg.v2 -> t -> approx option
+ = fun point t ->
+
+ Array.fold_left t
+ ~init:None
+ ~f:(fun res step ->
+ match step.move with
+ | Line p1 ->
+ let box = Gg.Box2.of_pts (Point.get_coord step.point) (Point.get_coord p1) in
+ begin match Gg.Box2.mem point box with
+ | false -> res
+ | true ->
+ (* TODO Evaluate the normal *)
+ res
+ end
+ | Curve bezier ->
+
+ let bezier' = Shapes.Bezier.(
+
+ { p0 = Point.get_coord step.point
+ ; p1 = Point.get_coord bezier.p1
+ ; ctrl0 = bezier.ctrl0
+ ; ctrl1 = bezier.ctrl1 }
+ ) in
+ let ratio, point' = Shapes.Bezier.get_closest_point point bezier' in
+ let distance' = Gg.V2.( norm (point - point') ) in
+ match res with
+ | Some {distance; _} when distance < distance' -> res
+ | _ -> Some
+ { closest_point = point'
+ ; distance = distance'
+ ; p0 = step.point
+ ; p1 = bezier.p1
+ ; ratio }
+ )
+
+ let map
+ : t -> (Point.t -> Point.t) -> t
+ = 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
+ { p1 = f bezier.p1
+ ; ctrl0 = Point.get_coord (f (Point.copy step.point bezier.ctrl0))
+ ; ctrl1 = Point.get_coord (f (Point.copy bezier.p1 bezier.ctrl1))
+ }
+ }
+ )
+
+ let iter
+ : t -> f:(Point.t -> unit) -> unit
+ = fun t ~f ->
+ Array.iter t
+ ~f:(fun step ->
+ match step.move with
+ | Line p2 -> f step.point; f p2
+ | Curve bezier -> f step.point ; f bezier.p1
+ )
+
+ let get_point'
+ : step -> Point.t
+ = fun { move ; _} ->
+ match move with
+ | Line p1 -> p1
+ | Curve bezier -> bezier.p1
+
+ (** Associate the return from the bezier point to an existing path *)
+ let assoc_point
+ : Shapes.Bezier.t -> step -> step
+ = fun bezier step ->
+ match step.move with
+ | Line p1
+ | Curve {p1; _} ->
+ let p0' = Point.copy step.point bezier.Shapes.Bezier.p0
+ and p1' = Point.copy p1 bezier.Shapes.Bezier.p1 in
+ { point = p0'
+ ; move = Curve
+ { p1 = p1'
+ ; ctrl0 = bezier.Shapes.Bezier.ctrl0
+ ; ctrl1 = bezier.Shapes.Bezier.ctrl1
+ }
+ }
+
+
+ let build_from_three_points p0 p1 p2 =
+ let bezier =
+ Shapes.Bezier.quadratic_to_cubic
+ @@ Shapes.Bezier.three_points_quadratic
+ (Point.get_coord p0)
+ (Point.get_coord p1)
+ (Point.get_coord p2) in
+
+ (* The middle point is not exactly at the middle anymore (it can have been
+ moved), we have the reevaluate it's position *)
+ let ratio, _ = Shapes.Bezier.get_closest_point
+ (Point.get_coord p1)
+ bezier in
+
+ let b0, b1 = Shapes.Bezier.slice ratio bezier in
+ let p0' = Point.copy p0 b0.Shapes.Bezier.p0
+ and p1' = Point.copy p1 b0.Shapes.Bezier.p1
+ and p2' = Point.copy p2 b1.Shapes.Bezier.p1 in
+
+ [| { 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 t ->
+
+ match Array.length t with
+ | 0 -> None
+ | 1 ->
+ let step = Array.get t 0 in
+ begin match step.move with
+ | Curve {p1; _}
+ | Line p1 ->
+ Some
+ [|
+ { point = step.point
+ ; move = Line p1 } |]
+ end
+ | 2 ->
+ 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 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
+
+ (* We process the whole curve in a single block *)
+ begin match Shapes.Bspline.to_bezier points with
+ | Error `InvalidPath -> None
+ | Ok beziers ->
+
+ (* Now for each point, reassociate the same point information,
+ We should have as many points as before *)
+ let rebuilded = Array.map2 beziers t ~f:assoc_point in
+ Some rebuilded
+ end
+
+ let find_pt_index
+ : Point.t -> step array -> int option
+ = fun point path ->
+ (* First search the element to remove. The counter mark the position of
+ the point to remove, not the segment itself. *)
+ let idx = ref None
+ and counter = ref 0 in
+
+ let _ = Array.exists
+ path
+ ~f:(fun element ->
+ let res =
+ if (Point.id element.point) = (Point.id point) then (
+ idx := Some (!counter) ;
+ true
+ ) else match element.move with
+ | Line p1
+ | Curve {p1;_} when (Point.id p1) = (Point.id point) ->
+ idx := Some (!counter+1) ;
+ true
+ | _ ->
+ false
+ in
+ incr counter;
+ res) in
+ !idx
+
+ let remove_point
+ : t -> Point.t -> t option
+ = fun t point ->
+
+ 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 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
+ [| { point = p0
+ ; move = Line p1 }|]
+ | _ -> None
+ end
+ | l ->
+ 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 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 t i) in
+ Some path
+ | Some n ->
+ let path' = Array.init (l-1)
+ ~f:(fun i ->
+ if i < (n-1) then
+ 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
+
+ We have to rebuild the point and set that
+ point_(-1).id = point_(+1).id
+ *)
+ let p0 = (Array.get t i).point in
+
+ match (Array.get t (i+1)).move with
+ | Line p1 ->
+ { point = p0
+ ; move = Line p1 }
+ | Curve c ->
+ { point = p0
+ ; move = Curve c }
+
+ else
+ Array.get t (i+1)
+ ) in
+ rebuild path'
+
+ let first_point
+ : step -> Point.t
+ = fun {point; _} -> point
+
+ let replace_point
+ : t -> Point.t -> t option
+ = fun t p ->
+
+ let add_path paths idx f points =
+ if 0 <= idx && idx < Array.length paths then
+ let path = Array.get t idx in
+ Point.get_coord (f path)
+ :: points
+ else points in
+
+ match Array.length t with
+ | 0 -> None
+ | 1 -> (* Only one point, easy ? *)
+ 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 [|
+ { point = p0
+ ; move = Line p1 }
+ |]
+ end
+
+ | 2 ->
+ 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 p0 p1 p2)
+
+ (* More than two segmend, it is ok for a partial reevaluation *)
+ | _ ->
+ match find_pt_index p t with
+ | None -> None
+ | Some n ->
+ let path = Array.copy t in
+
+ let p0, p1 =
+
+ if n < Array.length path then
+ p, get_point' (Array.get path n)
+ else
+ (Array.get path (n -1)).point, p
+ in
+
+ let min_idx = max (n-3) 0 in
+
+ let points =
+ add_path path (n-3) first_point
+ @@ add_path path (n-2) first_point
+ @@ add_path path (n-1) first_point
+ @@ (fun tl -> (Point.get_coord p)::tl)
+ @@ add_path path n get_point'
+ @@ add_path path (n+1) get_point'
+ @@ add_path path (n+2) get_point'
+ @@ [] in
+
+ (* It is impressive how fast it is to evaluate the curve ! Maybe is the
+ worker not required at all…
+ *)
+ let bezier_opt = Shapes.Bspline.to_bezier points in
+ begin match bezier_opt with
+ | Ok paths ->
+ Array.iteri paths
+ ~f:(fun i bezier ->
+ (* Only take two points before, and two after *)
+ let idx = min_idx + i in
+ if (n-2 < idx) && (idx < n +2) && idx < Array.length path then
+ Array.set path idx (assoc_point bezier (Array.get path idx))
+ );
+ Some path
+ | Error _ ->
+ let bezier', _ = Shapes.Bezier.three_points_quadratic
+ (Point.get_coord p)
+ (Point.get_coord @@ get_point' (Array.get path 0))
+ (Point.get_coord @@ get_point' (Array.get path 1))
+ |> Shapes.Bezier.quadratic_to_cubic
+ |> Shapes.Bezier.slice 0.5
+ in
+ Array.set path 0
+ { point = p0
+ ; move = (Curve
+ { ctrl0 = bezier'.Shapes.Bezier.ctrl0
+ ; ctrl1 = bezier'.Shapes.Bezier.ctrl1
+ ; p1
+ })
+ };
+ Some path
+ end
+end
diff --git a/script.it/path/fixed.mli b/script.it/path/fixed.mli
new file mode 100755
index 0000000..111187c
--- /dev/null
+++ b/script.it/path/fixed.mli
@@ -0,0 +1,81 @@
+(** Signature for points *)
+module type P = sig
+ type t
+
+ val get_coord : t -> Gg.v2
+
+ val id : t -> int
+
+ val copy : t -> Gg.v2 -> t
+
+end
+
+module Make(Point:P) : sig
+
+ module type BUILDER = sig
+ type t
+
+ val repr
+ : t -> (module Repr.M with type point = Point.t and type t = 's) -> 's -> 's
+
+ end
+
+ type t
+
+ (** Create a path from a builder *)
+ val to_fixed
+ : (module BUILDER with type t = 'a) -> 'a -> t
+
+ (** Represent the path *)
+ val repr
+ : t -> (module Repr.M with type point = Point.t and type t = 's) -> 's -> 's
+
+ (** Structure to represent all the required information for evaluating the
+ distance between a point and a path *)
+ type approx =
+ { distance : float
+ ; closest_point : Gg.v2
+ ; ratio : float
+ ; p0 : Point.t
+ ; p1 : Point.t }
+
+ (** Return the distance between a given point and the curve. May return
+ None if the point is out of the curve *)
+ val distance
+ : Gg.v2 -> t -> approx option
+
+ (** Iterate over a path *)
+ val iter
+ : t -> f:(Point.t -> unit) -> unit
+
+ (** Map all the points in the path *)
+ val map
+ : t -> (Point.t -> Point.t) -> t
+
+ (** Reevaluate all the control points on the path in order to get a smooth
+ curve *)
+ val rebuild
+ : t -> t option
+
+ (** Delete a point in the path.
+
+ Reconnect the path without the point removed, and reevaluate all the
+ control points from the nodes
+
+ return None if the point is not present in the curve
+ *)
+ val remove_point
+ : t -> Point.t -> t option
+
+ (** Replace a point by the given one.
+
+ An existing point with the same id shall be present in the path.
+
+ The path is not fully evaluated, and rebuild shall be runned in order to
+ get the path completely smooth.
+
+ *)
+ val replace_point
+ : t -> Point.t -> t option
+
+end
diff --git a/script.it/path/path.ml b/script.it/path/path.ml
new file mode 100755
index 0000000..ea90de4
--- /dev/null
+++ b/script.it/path/path.ml
@@ -0,0 +1,7 @@
+(** Common module for ensuring that the function is evaluated only once *)
+
+module Point = Point
+module Repr = Repr
+module Path_Builder = Builder.Make(Point)
+module Fixed = Fixed.Make(Point)
+
diff --git a/script.it/path/point.ml b/script.it/path/point.ml
new file mode 100755
index 0000000..ec6f8ad
--- /dev/null
+++ b/script.it/path/point.ml
@@ -0,0 +1,77 @@
+let internal_id = ref 0
+
+type t =
+ { p: Gg.v2
+ ; size : float
+ ; angle: float
+ ; stamp : float
+ ; id : int
+ }
+
+let empty =
+ { p = Gg.V2.of_tuple (0., 0.)
+ ; size = 0.
+ ; angle = 0.
+ ; stamp = 0.
+ ; id = 0
+ }
+
+let create ~angle ~width ~stamp ~x ~y =
+
+ incr internal_id;
+ { p = Gg.V2.v x y
+ ; size = width
+ ; angle = Gg.Float.rad_of_deg (180. -. angle )
+ ; stamp
+ ; id = !internal_id
+ }
+
+let copy point p =
+ { point with
+ p
+ }
+
+let set_angle p angle =
+ { p with angle = Gg.Float.rad_of_deg (180. -. angle) }
+
+let get_angle { angle; _} = 180. -. (Gg.Float.deg_of_rad angle)
+
+let set_width p size =
+ { p with size }
+
+let get_width { size; _} = size
+
+let (+) p1 p2 =
+ { p1 with p = Gg.V2.(+) p1.p p2 }
+
+let get_coord { p; _ } = p
+
+let get_stamp { stamp; _} = stamp
+
+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 mix
+ : float -> Gg.v2 -> t -> t -> t
+ = fun f point p0 p1 ->
+ let angle0 = p0.angle
+ and angle1 = p1.angle
+ and width0 = get_width p0
+ and width1 = get_width p1
+ and stamp0 = get_stamp p0
+ and stamp1 = get_stamp p1 in
+ let angle = angle0 +. f *. ( angle1 -. angle0 ) in
+ let width = width0 +. f *. ( width1 -. width0 ) in
+ let stamp = stamp0 +. f *. ( stamp1 -. stamp0 ) in
+ { p = point
+ ; size = width
+ ; angle
+ ; stamp
+ ; id = !internal_id
+ }
+
+let id { id; _} = id
diff --git a/script.it/path/point.mli b/script.it/path/point.mli
new file mode 100755
index 0000000..fe4cb45
--- /dev/null
+++ b/script.it/path/point.mli
@@ -0,0 +1,40 @@
+type t
+
+(** Return the point id. Each id is unique *)
+val id
+ : t -> int
+
+val empty : t
+
+val (+): t -> Gg.v2 -> t
+
+val get_coord : t -> Gg.v2
+
+val get_stamp : t -> float
+
+val create: angle:float -> width:float -> stamp:float -> x:float -> y:float -> t
+
+(** Return a copy of the point at given posistion
+
+ This is a true copy, the id will be the same for the two points
+ TODO : Should this be renamed set_position ?
+
+*)
+val copy : t -> Gg.v2 -> t
+
+val set_angle : t -> float -> t
+
+val get_angle : t -> float
+
+val set_width: t -> float -> t
+
+val get_width: t -> float
+
+val get_coord'
+ : t -> Gg.v2
+
+(** [mix f point p0 p1] create a new point at the position point, with the
+ characteristics from p0 and p1 *)
+val mix
+ : float -> Gg.v2 -> t -> t -> t
+
diff --git a/script.it/path/repr.ml b/script.it/path/repr.ml
new file mode 100755
index 0000000..17fd914
--- /dev/null
+++ b/script.it/path/repr.ml
@@ -0,0 +1,19 @@
+module type M = sig
+
+ type point
+
+ type t
+
+ (* Start a new path. *)
+ val start
+ : point -> t -> t
+
+ val line_to
+ : point -> point -> t -> t
+
+ val quadratic_to
+ : (point * Gg.v2 * Gg.v2 * point) -> t -> t
+
+ val stop
+ : t -> t
+end