From d43ccc61008bde91eecc59ace4b59a5ceb0ef889 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 1 Aug 2024 17:06:20 +0200 Subject: [PATCH] [Fonts] Remove Rendering related code --- src/Aardvark.Base.Fonts/BvhInternal.fs | 145 +++-- src/Aardvark.Base.Fonts/Font.fs | 334 +++-------- src/Aardvark.Base.Fonts/FontResolver.fs | 145 +++-- src/Aardvark.Base.Fonts/Path.fs | 336 +---------- src/Aardvark.Base.Fonts/PathSegment.fs | 531 +++++++++--------- src/Aardvark.Base.Fonts/PathTessellator.fs | 243 ++++---- src/Aardvark.sln | 7 + .../Aardvark.Base.Fonts.Tests.fsproj | 27 + src/Tests/Aardvark.Base.Fonts.Tests/Common.fs | 100 ++++ .../Aardvark.Base.Fonts.Tests/PathSegment.fs | 251 +++++++++ .../Aardvark.Base.Fonts.Tests/Program.fs | 10 + .../paket.references | 12 + .../test.runsettings | 15 + 13 files changed, 1036 insertions(+), 1120 deletions(-) create mode 100644 src/Tests/Aardvark.Base.Fonts.Tests/Aardvark.Base.Fonts.Tests.fsproj create mode 100644 src/Tests/Aardvark.Base.Fonts.Tests/Common.fs create mode 100644 src/Tests/Aardvark.Base.Fonts.Tests/PathSegment.fs create mode 100644 src/Tests/Aardvark.Base.Fonts.Tests/Program.fs create mode 100644 src/Tests/Aardvark.Base.Fonts.Tests/paket.references create mode 100644 src/Tests/Aardvark.Base.Fonts.Tests/test.runsettings diff --git a/src/Aardvark.Base.Fonts/BvhInternal.fs b/src/Aardvark.Base.Fonts/BvhInternal.fs index a4294e71..0565244d 100644 --- a/src/Aardvark.Base.Fonts/BvhInternal.fs +++ b/src/Aardvark.Base.Fonts/BvhInternal.fs @@ -1,5 +1,4 @@ -module internal Aardvark.Rendering.Text.BvhImpl - +module internal Aardvark.Base.Fonts.BvhImpl open Aardvark.Base open Aardvark.Base.Sorting @@ -24,8 +23,8 @@ module internal BvhNode3d = let inline getBounds (n : BvhNode3d<'K, 'V>) = match n with | Leaf(_,b,_) - | Node(_,_,b,_,_) -> b - + | Node(_,_,b,_,_) -> b + let inline count (n : BvhNode3d<'K, 'V>) = match n with | Leaf(_,_,v) -> v.Count @@ -56,7 +55,7 @@ module internal BvhNode3d = for dim in 0 .. 2 do - let getter = + let getter = match dim with | 0 -> fun (v : V3d) -> v.X | 1 -> fun (v : V3d) -> v.Y @@ -106,13 +105,13 @@ module internal BvhNode3d = for li in 0 .. lCnt - 1 do left.[li] <- arr.[bestPerm.[i]] i <- i + 1 - + for ri in 0 .. rCnt - 1 do right.[ri] <- arr.[bestPerm.[i]] i <- i + 1 Some (bestCost, bestlBox, HashMap.ofArrayV left, bestrBox, HashMap.ofArrayV right) - else + else None let rec build (limit : int) (bounds : Box3d) (elements : HashMap<'K, struct(Box3d * 'V)>) = @@ -122,7 +121,7 @@ module internal BvhNode3d = elif elements.Count <= limit then Leaf(0, bounds, elements) - else + else let bv = 1.0 / bounds.Volume match split bv elements with | Some (cost, lBox, lElements, rBox, rElements) -> @@ -147,7 +146,7 @@ module internal BvhNode3d = HashMap.union l r else HashMap.empty - + let rec getIntersectingFilter (query : Box3d) (filter : OptimizedClosures.FSharpFunc<'K, Box3d, 'V, bool>) (node : BvhNode3d<'K, 'V>) = match node with | Leaf(_, bounds, values) -> @@ -174,21 +173,21 @@ module internal BvhNode3d = match HashMap.tryFindV key values with | ValueSome (struct(ob, _ov)) -> // replace - if bounds.Contains ob then + if bounds.Contains ob then Leaf(overflowCount, b, HashMap.add key (struct(bounds, value)) values) else let mutable bb = bounds for struct(_,struct(b,_)) in values.ToSeqV() do bb.ExtendBy b Leaf(overflowCount, bb, HashMap.add key (struct(bounds, value)) values) - | _ -> + | _ -> // add let b = b.ExtendedBy bounds let vs = HashMap.add key (struct(bounds, value)) values - if vs.Count >= 2 * overflowCount && vs.Count > limit then + if vs.Count >= 2 * overflowCount && vs.Count > limit then build limit b vs - else + else Leaf(overflowCount, b, vs) | Node(bestCost, _, b, l, r) -> @@ -205,8 +204,8 @@ module internal BvhNode3d = if lCost < rCost then // add left if lCost > 2.0 * bestCost then - toSeq node - |> HashMap.ofSeqV + toSeq node + |> HashMap.ofSeqV |> HashMap.add key (struct(bounds, value)) |> build limit nb else @@ -217,9 +216,9 @@ module internal BvhNode3d = let nb = Box.Union(lb, rb) Node(min bestCost cc, lc + rc, nb, l, r) else - if rCost > 2.0 * bestCost then - toSeq node - |> HashMap.ofSeqV + if rCost > 2.0 * bestCost then + toSeq node + |> HashMap.ofSeqV |> HashMap.add key (struct(bounds, value)) |> build limit nb else @@ -229,13 +228,13 @@ module internal BvhNode3d = let cc = cost invVol lb lc rb rc let nb = Box.Union(lb, rb) Node(min bestCost cc, lc + rc, nb, l, r) - + let rec tryRemove (limit : int) (key : 'K) (bounds : Box3d) (node : BvhNode3d<'K, 'V>) = match node with | Leaf(overflowCount, b, values) -> if b.Intersects bounds then match HashMap.tryRemove key values with - | Some (struct(_,v), n) -> + | Some (struct(_,v), n) -> if n.Count > 0 then let mutable bb = Box3d.Invalid for struct(_,struct(b,_)) in n.ToSeqV() do @@ -253,7 +252,7 @@ module internal BvhNode3d = match tryRemove limit key bounds l with | Some (v, l) -> match l with - | Some l -> + | Some l -> let lc = count l let rc = count r let lb = getBounds l @@ -272,7 +271,7 @@ module internal BvhNode3d = match tryRemove limit key bounds r with | Some(v,r) -> match r with - | Some r -> + | Some r -> let lc = count l let rc = count r let lb = getBounds l @@ -291,7 +290,7 @@ module internal BvhNode3d = None else None - + let remove (limit : int) (key : 'K) (bounds : Box3d) (node : BvhNode3d<'K, 'V>) = match tryRemove limit key bounds node with | Some (_, rest) -> rest @@ -308,18 +307,18 @@ type BvhTree3d<'K, 'V> private(limit : int, root : option>, ke member x.Add(key : 'K, bounds : Box3d, value : 'V) = if bounds.IsValid then let keyBounds = HashMap.add key bounds keyBounds - let newRoot = + let newRoot = match root with | Some r -> BvhNode3d.add limit key bounds value r | None -> BvhNode3d.Leaf(0, bounds, HashMap.single key (struct(bounds, value))) BvhTree3d(limit, Some newRoot, keyBounds) - else + else x - + member x.Remove(key : 'K) = match HashMap.tryRemove key keyBounds with | Some (bounds, keyBounds) -> - let newRoot = + let newRoot = match root with | Some r -> BvhNode3d.remove limit key bounds r | None -> None @@ -330,7 +329,7 @@ type BvhTree3d<'K, 'V> private(limit : int, root : option>, ke member x.TryRemove(key : 'K) = match HashMap.tryRemove key keyBounds with | Some (bounds, keyBounds) -> - let newRoot = + let newRoot = match root with | Some r -> BvhNode3d.tryRemove limit key bounds r | None -> None @@ -341,14 +340,14 @@ type BvhTree3d<'K, 'V> private(limit : int, root : option>, ke None | None -> None - + member x.GetIntersecting(query : Box3d) = match root with | Some r -> BvhNode3d.getIntersecting query r | None -> HashMap.empty - + member x.GetIntersecting(query : Box3d, filter : 'K -> Box3d -> 'V -> bool) = match root with | Some r -> @@ -361,8 +360,8 @@ type BvhTree3d<'K, 'V> private(limit : int, root : option>, ke match root with | Some root -> BvhNode3d.toSeq root |> Seq.map (fun struct(k,struct(b,v)) -> k,b,v) | None -> Seq.empty - - member x.ToList() = + + member x.ToList() = x.ToSeq() |> Seq.toList member x.ToArray() = @@ -409,7 +408,7 @@ module BvhTree3d = let inline ofSeq (elements : seq<'K * Box3d * 'V>)= BvhTree3d.Build(splitLimit, elements) let inline ofList (elements : list<'K * Box3d * 'V>)= BvhTree3d.Build(splitLimit, elements) let inline ofArray (elements : array<'K * Box3d * 'V>)= BvhTree3d.Build(splitLimit, elements) - + let inline toSeq (tree : BvhTree3d<'K, 'V>)= tree.ToSeq() let inline toList (tree : BvhTree3d<'K, 'V>)= tree.ToList() let inline toArray (tree : BvhTree3d<'K, 'V>)= tree.ToArray() @@ -440,8 +439,8 @@ module internal BvhNode2d = let inline getBounds (n : BvhNode2d<'K, 'V>) = match n with | Leaf(_,b,_) - | Node(_,_,b,_,_) -> b - + | Node(_,_,b,_,_) -> b + let inline count (n : BvhNode2d<'K, 'V>) = match n with | Leaf(_,_,v) -> v.Count @@ -472,7 +471,7 @@ module internal BvhNode2d = for dim in 0 .. 1 do - let getter = + let getter = match dim with | 0 -> fun (v : V2d) -> v.X | _ -> fun (v : V2d) -> v.Y @@ -521,13 +520,13 @@ module internal BvhNode2d = for li in 0 .. lCnt - 1 do left.[li] <- arr.[bestPerm.[i]] i <- i + 1 - + for ri in 0 .. rCnt - 1 do right.[ri] <- arr.[bestPerm.[i]] i <- i + 1 Some (bestCost, bestlBox, HashMap.ofArrayV left, bestrBox, HashMap.ofArrayV right) - else + else None let rec build (limit : int) (bounds : Box2d) (elements : HashMap<'K, struct(Box2d * 'V)>) = @@ -537,7 +536,7 @@ module internal BvhNode2d = elif elements.Count <= limit then Leaf(0, bounds, elements) - else + else let bv = 1.0 / bounds.Area match split bv elements with | Some (cost, lBox, lElements, rBox, rElements) -> @@ -565,7 +564,7 @@ module internal BvhNode2d = HashMap.union l r else HashMap.empty - + let rec getIntersectingFilter (query : Box2d) (filter : OptimizedClosures.FSharpFunc<'K, Box2d, 'V, option<'T>>) (node : BvhNode2d<'K, 'V>) = match node with | Leaf(_, bounds, values) -> @@ -592,21 +591,21 @@ module internal BvhNode2d = match HashMap.tryFindV key values with | ValueSome (struct(ob, _ov)) -> // replace - if bounds.Contains ob then + if bounds.Contains ob then Leaf(overflowCount, b, HashMap.add key (struct(bounds, value)) values) else let mutable bb = bounds for struct(_,struct(b,_)) in values.ToSeqV() do bb.ExtendBy b Leaf(overflowCount, bb, HashMap.add key (struct(bounds, value)) values) - | _ -> + | _ -> // add let b = b.ExtendedBy bounds let vs = HashMap.add key (struct(bounds, value)) values - if vs.Count >= 2 * overflowCount && vs.Count > limit then + if vs.Count >= 2 * overflowCount && vs.Count > limit then build limit b vs - else + else Leaf(overflowCount, b, vs) | Node(bestCost, _, b, l, r) -> @@ -623,8 +622,8 @@ module internal BvhNode2d = if lCost < rCost then // add left if lCost > 2.0 * bestCost then - toSeq node - |> HashMap.ofSeqV + toSeq node + |> HashMap.ofSeqV |> HashMap.add key (struct(bounds, value)) |> build limit nb else @@ -635,9 +634,9 @@ module internal BvhNode2d = let nb = Box.Union(lb, rb) Node(min bestCost cc, lc + rc, nb, l, r) else - if rCost > 2.0 * bestCost then - toSeq node - |> HashMap.ofSeqV + if rCost > 2.0 * bestCost then + toSeq node + |> HashMap.ofSeqV |> HashMap.add key (struct(bounds, value)) |> build limit nb else @@ -647,13 +646,13 @@ module internal BvhNode2d = let cc = cost invVol lb lc rb rc let nb = Box.Union(lb, rb) Node(min bestCost cc, lc + rc, nb, l, r) - + let rec tryRemove (limit : int) (key : 'K) (bounds : Box2d) (node : BvhNode2d<'K, 'V>) = match node with | Leaf(overflowCount, b, values) -> if b.Intersects bounds then match HashMap.tryRemove key values with - | Some (struct(_,v), n) -> + | Some (struct(_,v), n) -> if n.Count > 0 then let mutable bb = Box2d.Invalid for struct(_,struct(b,_)) in n.ToSeqV() do @@ -671,7 +670,7 @@ module internal BvhNode2d = match tryRemove limit key bounds l with | Some (v, l) -> match l with - | Some l -> + | Some l -> let lc = count l let rc = count r let lb = getBounds l @@ -690,7 +689,7 @@ module internal BvhNode2d = match tryRemove limit key bounds r with | Some(v,r) -> match r with - | Some r -> + | Some r -> let lc = count l let rc = count r let lb = getBounds l @@ -709,7 +708,7 @@ module internal BvhNode2d = None else None - + let remove (limit : int) (key : 'K) (bounds : Box2d) (node : BvhNode2d<'K, 'V>) = match tryRemove limit key bounds node with | Some (_, rest) -> rest @@ -726,18 +725,18 @@ type BvhTree2d<'K, 'V> private(limit : int, root : option>, ke member x.Add(key : 'K, bounds : Box2d, value : 'V) = if bounds.IsValid then let keyBounds = HashMap.add key bounds keyBounds - let newRoot = + let newRoot = match root with | Some r -> BvhNode2d.add limit key bounds value r | None -> BvhNode2d.Leaf(0, bounds, HashMap.single key (struct(bounds, value))) BvhTree2d(limit, Some newRoot, keyBounds) - else + else x - + member x.Remove(key : 'K) = match HashMap.tryRemove key keyBounds with | Some (bounds, keyBounds) -> - let newRoot = + let newRoot = match root with | Some r -> BvhNode2d.remove limit key bounds r | None -> None @@ -748,7 +747,7 @@ type BvhTree2d<'K, 'V> private(limit : int, root : option>, ke member x.TryRemove(key : 'K) = match HashMap.tryRemove key keyBounds with | Some (bounds, keyBounds) -> - let newRoot = + let newRoot = match root with | Some r -> BvhNode2d.tryRemove limit key bounds r | None -> None @@ -759,14 +758,14 @@ type BvhTree2d<'K, 'V> private(limit : int, root : option>, ke None | None -> None - + member x.GetIntersecting(query : Box2d) = match root with | Some r -> BvhNode2d.getIntersecting (ref 0) query r | None -> HashMap.empty - + member x.GetIntersecting(query : Box2d, filter : 'K -> Box2d -> 'V -> option<'T>) = match root with | Some r -> @@ -779,8 +778,8 @@ type BvhTree2d<'K, 'V> private(limit : int, root : option>, ke match root with | Some root -> BvhNode2d.toSeq root |> Seq.map (fun struct(k,struct(b,v)) -> k,b,v) | None -> Seq.empty - - member x.ToList() = + + member x.ToList() = x.ToSeq() |> Seq.toList member x.ToArray() = @@ -829,7 +828,7 @@ module BvhTree2d = let inline ofSeq (elements : seq<'K * Box2d * 'V>)= BvhTree2d.Build(splitLimit, elements) let inline ofList (elements : list<'K * Box2d * 'V>)= BvhTree2d.Build(splitLimit, elements) let inline ofArray (elements : array<'K * Box2d * 'V>)= BvhTree2d.Build(splitLimit, elements) - + let inline toSeq (tree : BvhTree2d<'K, 'V>)= tree.ToSeq() let inline toList (tree : BvhTree2d<'K, 'V>)= tree.ToList() let inline toArray (tree : BvhTree2d<'K, 'V>)= tree.ToArray() @@ -839,13 +838,13 @@ module BvhTree2d = for (k,b,v) in toSeq r do l <- l.Add(k,b,v) l - + module BvhTest = - + let ofSeq (limit : int) (bounds : seq) = BvhTree2d.Build(limit, bounds |> Seq.mapi (fun i b -> i,b,b)) - + let ofSeqStupid (limit : int) (bounds : seq) = let mutable t = BvhTree2d.Empty limit for (i, b) in Seq.indexed bounds do @@ -866,10 +865,10 @@ module BvhTest = let limit = 24 for cnt in [1024; 65536] do Log.start "%d boxes" cnt - let boxes = + let boxes = Array.init cnt (fun _ -> randomBox()) - let bruteForce (q : Box2d) = + let bruteForce (q : Box2d) = let mutable res = HashSet.empty for i in 0 .. boxes.Length - 1 do let b = boxes.[i] @@ -886,7 +885,7 @@ module BvhTest = let bvhs = ofSeqStupid limit boxes sw.Stop() Log.line "incr took: %A" sw.MicroTime - + sw.Restart() let mutable r = bvhs for i in 0 .. bvhs.Count - 1 do @@ -906,7 +905,7 @@ module BvhTest = let reference = bruteForce q swf.Stop() - + swb.Start() let build = bvh.GetIntersecting q swb.Stop() @@ -927,7 +926,7 @@ module BvhTest = | Add(_,i) -> Report.WarnNoPrefix("missing {0}", i) | Rem(_,i) -> Report.WarnNoPrefix("wrong {0}", i) Log.stop() - + let delta = HashSet.computeDelta incr reference if not (HashSetDelta.isEmpty delta) then Log.start "incr error" @@ -938,7 +937,7 @@ module BvhTest = | Add(_,i) -> Report.WarnNoPrefix("missing {0}", i) | Rem(_,i) -> Report.WarnNoPrefix("wrong {0}", i) Log.stop() - + Log.line "force: %A" (swf.MicroTime / iter) Log.line "build: %A" (swb.MicroTime / iter) Log.line "incr: %A" (swi.MicroTime / iter) diff --git a/src/Aardvark.Base.Fonts/Font.fs b/src/Aardvark.Base.Fonts/Font.fs index 7938470a..c882746b 100644 --- a/src/Aardvark.Base.Fonts/Font.fs +++ b/src/Aardvark.Base.Fonts/Font.fs @@ -1,15 +1,8 @@ -namespace Aardvark.Rendering.Text - -#nowarn "9" -#nowarn "51" +namespace Aardvark.Base.Fonts open System -open System.Collections.Concurrent open System.Runtime.CompilerServices open Aardvark.Base -open Aardvark.Rendering -open Typography.OpenFont.Extensions - [] type FontStyle = @@ -20,7 +13,7 @@ type FontStyle = type Shape(path : Path, windingRule : WindingRule) = - static let quad = + static let quad = Shape( Path.ofList [ PathSegment.line V2d.OO V2d.OI @@ -31,19 +24,19 @@ type Shape(path : Path, windingRule : WindingRule) = WindingRule.NonZero ) - let mutable geometry = None - + let mutable geometry = ValueNone + static member Quad = quad member x.Path = path member x.Geometry = match geometry with - | Some g -> g - | None -> - let g = Path.toGeometry windingRule path - geometry <- Some g - g + | ValueSome g -> g + | ValueNone -> + let g = Path.toGeometry windingRule path + geometry <- ValueSome g + g new(path : Path) = Shape(path, WindingRule.NonZero) @@ -74,7 +67,7 @@ module private Typography = member x.BeginRead(_) = () member x.EndRead() = () - member x.CloseContour() = + member x.CloseContour() = match start, pos with | Some s, Some p -> if not (Fun.ApproximateEquals(p, s, bb.Size.NormMax * 1E-8)) then @@ -95,7 +88,7 @@ module private Typography = | None -> start <- Some pa | _ -> () - member x.LineTo(xa, ya) = + member x.LineTo(xa, ya) = match pos with | Some p0 -> let pa = V2d(xa, ya) * scale @@ -121,8 +114,8 @@ module private Typography = | None -> () //failwith "asdasd" - - member x.Curve4(xa, ya, xb, yb, xc, yc) = + + member x.Curve4(xa, ya, xb, yb, xc, yc) = match pos with | Some p0 -> let pa = V2d(xa, ya) * scale @@ -131,7 +124,7 @@ module private Typography = bb.ExtendBy pa bb.ExtendBy pb bb.ExtendBy pc - + match PathSegment.tryBezier3 p0 pa pb pc with | Some b -> list.Add b | None -> () @@ -160,20 +153,20 @@ module private Typography = // let mutable o = i + 1 // while o < g.GlyphPoints.Length && not g.GlyphPoints.[o].OnCurve do // o <- o + 1 - + // if o < g.GlyphPoints.Length then // let len = 1 + o - i // match len with - // | 2 -> + // | 2 -> // let p0 = g.GlyphPoints.[i] |> GlyphPoint.toV2d scale // let p1 = g.GlyphPoints.[o] |> GlyphPoint.toV2d scale // yield PathSegment.line p0 p1 - // | 3 -> + // | 3 -> // let p0 = g.GlyphPoints.[i] |> GlyphPoint.toV2d scale // let a = g.GlyphPoints.[i+1] |> GlyphPoint.toV2d scale // let p1 = g.GlyphPoints.[o] |> GlyphPoint.toV2d scale // yield PathSegment.bezier2 p0 a p1 - // | 4 -> + // | 4 -> // let p0 = g.GlyphPoints.[i] |> GlyphPoint.toV2d scale // let a = g.GlyphPoints.[i+1] |> GlyphPoint.toV2d scale // let b = g.GlyphPoints.[i+2] |> GlyphPoint.toV2d scale @@ -183,29 +176,29 @@ module private Typography = // | _ -> // failwithf "invalid path segment: %A" len - // i <- o + // i <- o //|] - - - + + + [] type CodePoint(value : int) = member x.Value = value - + static member Invalid = CodePoint(-1) member private x.AsString = x.ToString() - override x.ToString() = + override x.ToString() = if value < 0 then "invalid" else sprintf "U+%X" value member x.String = - if value &&& 0x10000 = 0 then + if value &&& 0x10000 = 0 then System.String(char value, 1) - else + else let value = value - 65536 let c0 = char (0xD800 ||| ((value >>> 10))) let c1 = char (0xDC00 ||| (value &&& 0x3FF)) @@ -233,7 +226,7 @@ type CodePointStringExtensions private() = for ii in 1 .. str.Length - 1 do let c1 = str.[ii] |> uint16 - if lastSurrogate then + if lastSurrogate then let v1 = c1 &&& 0x23FFus let code = 0x10000 ||| (int c0 <<< 10) ||| int v1 arr.[oi] <- CodePoint(code) @@ -245,21 +238,21 @@ type CodePointStringExtensions private() = else arr.[oi] <- CodePoint(int c1) oi <- oi + 1 - + if arr.Length > oi then System.Array.Resize(&arr, oi) arr else [||] - + [] static member GetString(codepoints : CodePoint[]) = let sb = System.Text.StringBuilder() for c in codepoints do let value = c.Value - if value &&& 0x10000 = 0 then + if value &&& 0x10000 = 0 then sb.Append(char value) |> ignore - else + else let value = value - 65536 let c0 = char (0xD800 ||| ((value >>> 10))) let c1 = char (0xDC00 ||| (value &&& 0x3FF)) @@ -271,16 +264,16 @@ type CodePointStringExtensions private() = type Glyph internal(g : Typography.OpenFont.Glyph, isValid : bool, scale : float, advance : float, bearing : float, c : CodePoint) = inherit Shape(Path.ofGlyph scale g, WindingRule.NonZero) - let widths = + let widths = let width = float (g.MaxX - g.MinX) * scale V3d(bearing, width, advance - width - bearing) - + member x.CodePoint = c member x.IsValid = isValid member x.Bounds = base.Path.bounds member x.Path = base.Path - member x.Advance = + member x.Advance = let sizes = widths sizes.X + sizes.Y + sizes.Z @@ -292,11 +285,11 @@ type private FontImpl internal(f : Typeface, familyName : string, weight : int, let scale = f.CalculateScaleToPixel 1.0f |> float let glyphCache = Dict() - + let lineHeight = float (f.Ascender - f.Descender + f.LineGap) * scale - let spacing = + let spacing = let idx = f.GetGlyphIndex(int ' ') |> int - + if idx >= 0 && idx < f.Glyphs.Length then let g = f.Glyphs.[idx] if g.HasOriginalAdvancedWidth then @@ -304,18 +297,18 @@ type private FontImpl internal(f : Typeface, familyName : string, weight : int, else let a = f.GetHAdvanceWidthFromGlyphIndex(uint16 idx) float a * scale - else + else float (f.GetAdvanceWidth(idx)) * scale - - + + // https://docs.microsoft.com/en-us/windows/desktop/gdi/string-widths-and-heights let ascent = float f.Ascender * scale let descent = float -f.Descender * scale - let lineGap = float f.LineGap * scale + let lineGap = float f.LineGap * scale let internalLeading = float ((f.Bounds.YMax - f.Bounds.YMin) - (f.Ascender - f.Descender) - f.LineGap) * scale let externalLeading = lineGap - internalLeading - static let symbola = + static let symbola = lazy ( let assembly = typeof.Assembly let resName = assembly.GetManifestResourceNames() |> Array.find (fun n -> n.EndsWith "Symbola.ttf") @@ -327,19 +320,19 @@ type private FontImpl internal(f : Typeface, familyName : string, weight : int, lock glyphCache (fun () -> glyphCache.GetOrCreate(c, fun c -> let idx = f.GetGlyphIndex(c.Value) - if idx = 0us && c.Value > 65535 && symbola.Value <> self then + if idx = 0us && c.Value > 65535 && symbola.Value <> self then symbola.Value.GetGlyph c else let glyph = f.Glyphs.[int idx] - let advance = + let advance = if glyph.HasOriginalAdvancedWidth then float glyph.OriginalAdvanceWidth * scale else let a = f.GetHAdvanceWidthFromGlyphIndex(idx) float a * scale - - let bearing = + + let bearing = let a = f.GetHFrontSideBearingFromGlyphIndex(idx) float a * scale Glyph(glyph, idx > 0us, scale, advance, bearing, c) @@ -357,7 +350,7 @@ type private FontImpl internal(f : Typeface, familyName : string, weight : int, member x.ExternalLeading = externalLeading member x.Weight = weight member x.Italic = italic - + member x.Style = if weight >= 700 then if italic then FontStyle.BoldItalic @@ -365,7 +358,7 @@ type private FontImpl internal(f : Typeface, familyName : string, weight : int, else if italic then FontStyle.Italic else FontStyle.Regular - + member x.Spacing = spacing @@ -382,28 +375,28 @@ type private FontImpl internal(f : Typeface, familyName : string, weight : int, new(file : string, ?weight : int, ?italic : bool) = let weight = defaultArg weight 400 let italic = defaultArg italic false - let entry = + let entry = FontResolver.FontTableEntries.ofFile file |> FontResolver.FontTableEntries.chooseBestEntry weight italic - + let face = entry |> FontResolver.FontTableEntries.read System.IO.File.OpenRead FontImpl(face, entry.FamilyName, entry.Weight, entry.Italic) new(name : string, openStream : unit -> System.IO.Stream, ?weight : int, ?italic : bool) = let weight = defaultArg weight 400 let italic = defaultArg italic false - let entry = + let entry = FontResolver.FontTableEntries.ofStream () openStream |> FontResolver.FontTableEntries.chooseBestEntry weight italic - - + + let face = entry |> FontResolver.FontTableEntries.read openStream FontImpl(face, name, entry.Weight, entry.Italic) type Font private(impl : FontImpl, family : string) = - static let symbola = + static let symbola = lazy ( let impl = FontImpl.Symbola new Font(impl, impl.Family) @@ -412,16 +405,16 @@ type Font private(impl : FontImpl, family : string) = static let systemTable = System.Collections.Concurrent.ConcurrentDictionary() static let fileTable = System.Collections.Concurrent.ConcurrentDictionary() - + static let copyStream (stream : System.IO.Stream) = - let arr = + let arr = use data = new System.IO.MemoryStream() stream.CopyTo(data) data.ToArray() - + fun () -> new System.IO.MemoryStream(arr) :> System.IO.Stream - - + + static member Symbola = symbola.Value member x.Family = family @@ -445,10 +438,10 @@ type Font private(impl : FontImpl, family : string) = impl ) Font(impl, impl.Family) - + static member Load(file : string) = Font.Load(file, 400, false) - + static member Load(file : string, style : FontStyle) = let weight = match style with @@ -459,12 +452,12 @@ type Font private(impl : FontImpl, family : string) = | FontStyle.Italic | FontStyle.BoldItalic -> true | _ -> false Font.Load(file, weight, italic) - + new(stream : System.IO.Stream, weight : int, italic : bool) = let openStream = copyStream stream let impl = FontImpl("Stream", openStream, weight, italic) Font(impl, impl.Family) - + new(stream : System.IO.Stream, style : FontStyle) = let weight = match style with @@ -475,7 +468,7 @@ type Font private(impl : FontImpl, family : string) = | FontStyle.Italic | FontStyle.BoldItalic -> true | _ -> false Font(stream, weight, italic) - + new(stream : System.IO.Stream) = Font(stream, 400, false) @@ -487,7 +480,7 @@ type Font private(impl : FontImpl, family : string) = impl ) Font(impl, family) - + new(family : string, style : FontStyle) = let weight = match style with @@ -498,198 +491,9 @@ type Font private(impl : FontImpl, family : string) = | FontStyle.Italic | FontStyle.BoldItalic -> true | _ -> false Font(family, weight, italic) - - - new(family : string) = Font(family, 400, false) - - -module FontRenderingSettings = - let mutable DisableSampleShading = false - - -type ShapeCache(r : IRuntime) = - static let cache = ConcurrentDictionary>() - - let types = - Map.ofList [ - DefaultSemantic.Positions, typeof - Path.Attributes.KLMKind, typeof - ] - - let pool = r.CreateGeometryPool(types) - let ranges = ConcurrentDictionary() - - - let surfaceCache = ConcurrentDictionary>() - let boundarySurfaceCache = ConcurrentDictionary>() - let billboardSurfaceCache = ConcurrentDictionary>() - - - let pathShader = - if FontRenderingSettings.DisableSampleShading then - Path.Shader.pathFragmentNoSampleShading |> toEffect - else - Path.Shader.pathFragment |> toEffect - - let effect = - FShade.Effect.compose [ - Path.Shader.pathVertex |> toEffect - //Path.Shader.pathTrafo |> toEffect - Path.Shader.depthBiasVs |> toEffect - pathShader - ] - - let instancedEffect = - FShade.Effect.compose [ - Path.Shader.pathVertexInstanced |> toEffect - Path.Shader.depthBiasVs |> toEffect - pathShader - ] - - let boundaryEffect = - FShade.Effect.compose [ - Path.Shader.boundaryVertex |> toEffect - Path.Shader.boundary |> toEffect - ] - - let instancedBoundaryEffect = - FShade.Effect.compose [ - DefaultSurfaces.instanceTrafo |> toEffect - Path.Shader.boundaryVertex |> toEffect - Path.Shader.boundary |> toEffect - ] - - let billboardEffect = - FShade.Effect.compose [ - Path.Shader.pathVertexBillboard |> toEffect - Path.Shader.depthBiasVs |> toEffect - pathShader - ] - - let instancedBillboardEffect = - FShade.Effect.compose [ - Path.Shader.pathVertexInstancedBillboard |> toEffect - Path.Shader.depthBiasVs |> toEffect - pathShader - ] - - let surface (s : IFramebufferSignature) = - surfaceCache.GetOrAdd(s, fun s -> - lazy ( - r.PrepareEffect( - s, [ - Path.Shader.pathVertex |> toEffect - pathShader - ] - ) - ) - ).Value - - let boundarySurface (s : IFramebufferSignature) = - boundarySurfaceCache.GetOrAdd(s, fun s -> - lazy ( - r.PrepareEffect( - s, [ - Path.Shader.boundaryVertex |> toEffect - Path.Shader.boundary |> toEffect - ] - ) - ) - ).Value - - let billboardSurface (s : IFramebufferSignature) = - billboardSurfaceCache.GetOrAdd(s, fun s -> - lazy ( - r.PrepareEffect( - s, [ - Path.Shader.pathVertexBillboard |> toEffect - Path.Shader.boundary |> toEffect - ] - ) - ) - ).Value - - - do - r.OnDispose.Add(fun () -> - for x in boundarySurfaceCache.Values do r.DeleteSurface x.Value - for x in surfaceCache.Values do r.DeleteSurface x.Value - for x in billboardSurfaceCache.Values do r.DeleteSurface x.Value - pool.Dispose() - ranges.Clear() - cache.Clear() - ) - - let vertexBuffers = - { new IAttributeProvider with - member x.All = Seq.empty - member x.TryGetAttribute(sem) = - match pool.TryGetBufferView sem with - | Some bufferView -> - Some bufferView - | None -> - None - - member x.Dispose() = () - } - - static member GetOrCreateCache(r : IRuntime) = - cache.GetOrAdd(r, fun r -> - lazy (new ShapeCache(r)) - ).Value - - member x.Effect = effect - member x.InstancedEffect = instancedEffect - member x.BoundaryEffect = boundaryEffect - member x.InstancedBoundaryEffect = instancedBoundaryEffect - member x.BillboardEffect = billboardEffect - member x.InstancedBillboardEffect = instancedBillboardEffect - member x.Surface s = surface s - member x.BoundarySurface s = boundarySurface s - member x.BillboardSurface s = billboardSurface s - member x.VertexBuffers = vertexBuffers - - member x.GetBufferRange(shape : Shape) = - ranges.GetOrAdd(shape, fun shape -> - let ptr = pool.Alloc(shape.Geometry) - let last = ptr.Offset + ptr.Size - 1n |> int - let first = ptr.Offset |> int - Range1i(first, last) - ) - - member x.PrepareShaders(signature : IFramebufferSignature) = - let _ = surface signature - let _ = boundarySurface signature - () - - - member x.Dispose() = - pool.Dispose() - ranges.Clear() - -[] -type PrepareFontExtensions private() = - - [] - static member PrepareGlyphs(r : IRuntime, f : Font, chars : seq) = - let cache = ShapeCache.GetOrCreateCache r - - for c in chars do - cache.GetBufferRange (f.GetGlyph(c)) |> ignore - - [] - static member PrepareGlyphs(r : IRuntime, f : Font, chars : seq) = - let cache = ShapeCache.GetOrCreateCache r - - for c in chars do - cache.GetBufferRange (f.GetGlyph(CodePoint c)) |> ignore - - [] - static member PrepareTextShaders(r : IRuntime, f : Font, signature : IFramebufferSignature) = - let cache = ShapeCache.GetOrCreateCache r - cache.PrepareShaders signature + new(family : string) = Font(family, 400, false) [] module Font = @@ -710,8 +514,4 @@ module Glyph = let inline path (g : Glyph) = g.Path let inline geometry (g : Glyph) = g.Geometry let inline codePoint (g : Glyph) = g.CodePoint - let inline string (g : Glyph) = g.CodePoint.String - - - - + let inline string (g : Glyph) = g.CodePoint.String \ No newline at end of file diff --git a/src/Aardvark.Base.Fonts/FontResolver.fs b/src/Aardvark.Base.Fonts/FontResolver.fs index cf670901..1049b7ba 100644 --- a/src/Aardvark.Base.Fonts/FontResolver.fs +++ b/src/Aardvark.Base.Fonts/FontResolver.fs @@ -1,18 +1,15 @@ -namespace Aardvark.Rendering.Text +namespace Aardvark.Base.Fonts open Aardvark.Base open System open System.IO open System.Runtime.InteropServices -open Microsoft.FSharp.NativeInterop -open System.Security -open FuzzySharp open Typography.OpenFont #nowarn "9" module internal FontResolver = - + type FontTableEntry<'a> = { Tag : 'a @@ -22,7 +19,7 @@ module internal FontResolver = Italic : bool SubFamilyName : string } - + module FontTableEntries = // resolve according to: https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight let chooseBestEntry (weight : int) (italic : bool) (available : list>) = @@ -38,11 +35,11 @@ module internal FontResolver = match nonitalic with | [] -> available | _ -> nonitalic - + let bestEntry = let map = members |> List.map (fun m -> m.Weight, m) |> MapExt.ofList let (l, s, r) = MapExt.neighbours weight map - + match s with | Some (_, m) -> m | None -> @@ -60,21 +57,21 @@ module internal FontResolver = | None -> // If no match is found, look for available weights greater than 500, in ascending order. Option.get r |> snd - + elif weight < 400 then // If a weight less than 400 is given, look for available weights less than the target, in descending order. // If no match is found, look for available weights greater than the target, in ascending order. match l with | Some (_, lm) -> lm | None -> Option.get r |> snd - + else // If a weight greater than 500 is given, look for available weights greater than the target, in ascending order. // If no match is found, look for available weights less than the target, in descending order. match r with | Some (_, rm) -> rm | None -> Option.get l |> snd - + bestEntry let ofStream (tag : 'a) (openStream : unit -> #Stream) = @@ -86,9 +83,9 @@ module internal FontResolver = Offset = info.ActualStreamOffset Weight = int info.Weight Italic = info.OS2TranslatedStyle.HasFlag Extensions.TranslatedOS2FontStyle.ITALIC || info.OS2TranslatedStyle.HasFlag Extensions.TranslatedOS2FontStyle.OBLIQUE - SubFamilyName = info.SubFamilyName + SubFamilyName = info.SubFamilyName } - + let r = OpenFontReader() use s = openStream() :> System.IO.Stream let info = r.ReadPreview s @@ -98,56 +95,56 @@ module internal FontResolver = [ofInfo info] with _ -> [] - + let ofFile (file : string) = if System.IO.File.Exists file then ofStream file (fun () -> System.IO.File.OpenRead file) else [] - + let read (openStream : 'a -> #System.IO.Stream) (entry : FontTableEntry<'a>) = let reader = OpenFontReader() use s = openStream entry.Tag :> System.IO.Stream reader.Read(s, entry.Offset, ReadFlags.Full) - - + + type FontTable<'a> (entries : seq>) = - + static let normalizeFamilyName (name : string) = name.ToLowerInvariant().Trim() - - - + + + let table = let dict = System.Collections.Generic.Dictionary>() - + for e in entries do if not (isNull e.FamilyName) then let key = normalizeFamilyName e.FamilyName - + match dict.TryGetValue key with | (true, s) -> dict.[key] <- e :: s | _ -> dict.[key] <- [e] - + dict - + let keys = table |> Seq.collect (fun (KeyValue(key, e)) -> e |> Seq.map (fun e -> e.FamilyName, key)) |> Seq.toArray - + let names = keys |> Array.map fst - - + + member x.Find(family : string, weight : int, italic : bool) = let res = FuzzySharp.Process.ExtractOne(family, names) let (_, key) = keys.[res.Index] let entries = table.[key] FontTableEntries.chooseBestEntry weight italic entries - + module private Win32 = open System.Runtime.InteropServices @@ -194,7 +191,7 @@ module internal FontResolver = let mutable run = true let nameRx = System.Text.RegularExpressions.Regex(@"^(.*?)[ \t]*(Bold Italic|Light Italic|Thin Italic|Bold|Semibold|Thin|Italic|Light)?[ \t]*\([ \t]*(TrueType|OpenType)[ \t]*\)[ \t]*$", System.Text.RegularExpressions.RegexOptions.IgnoreCase) - + let result = System.Collections.Generic.List<_>() let fonts = Environment.GetFolderPath Environment.SpecialFolder.Fonts while run do @@ -207,14 +204,14 @@ module internal FontResolver = use pValue = fixed valueBuffer use pName = fixed nameBuffer - - - + + + let ret = RegEnumValue(key, index, pName, &nameLen, 0n, 0n, pValue, &valueLen) if ret = 0 then let name = System.Text.Encoding.ASCII.GetString(nameBuffer, 0, nameLen).Trim(' ', char 0) let file = System.Text.Encoding.ASCII.GetString(valueBuffer, 0, valueLen).Trim(' ', char 0) - + let familyName = let m = nameRx.Match name if m.Success then @@ -223,7 +220,7 @@ module internal FontResolver = else Some value else None - + let path = Path.Combine(fonts, file) if File.Exists path then let entries = FontTableEntries.ofFile path @@ -232,12 +229,12 @@ module internal FontResolver = for r in entries do result.Add { r with FamilyName = f } | _ -> result.AddRange entries - - + + index <- index + 1 else run <- false - + FontTable result ) @@ -257,14 +254,14 @@ module internal FontResolver = [] let private CoreGraphics = "/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics" - + [] let private CoreText = "/System/Library/Frameworks/CoreText.framework/Versions/A/CoreText" - - + + [] type CFRange = { Start : nativeint; Length : nativeint } - + [] extern void* CFDictionaryCreateMutable (void* a, int cap, void* b, void* c) @@ -301,83 +298,83 @@ module internal FontResolver = let NSFontFamilyAttribute = CFStringCreateWithCString(0n, "NSFontFamilyAttribute", 0n) let NSFontFaceAttribute = CFStringCreateWithCString(0n, "NSFontFaceAttribute", 0n) let NSCTFontFileURLAttribute = CFStringCreateWithCString(0n, "NSCTFontFileURLAttribute", 0n) - - - + + + let ptr = CFDictionaryCreateMutable (0n, 100000, 0n, 0n) - - + + let coll = CTFontCollectionCreateFromAvailableFonts ptr let arr = CTFontCollectionCreateMatchingFontDescriptors coll let cnt = CFArrayGetCount arr - + let mutable range = { Start = 0n; Length = nativeint cnt } - + let getString (font : nativeint) (att : nativeint) = let test = CTFontDescriptorCopyAttribute(font, att) let buffer = Array.zeroCreate 8192 use ptr = fixed buffer CFStringGetCString(test, ptr, 8192, 0n) - - + + let mutable l = 0 while l < buffer.Length && buffer.[l] <> 0uy do l <- l + 1 - - - + + + System.Text.Encoding.UTF8.GetString(buffer,0, l) - + let getPath (font : nativeint) (att : nativeint) = let test = CTFontDescriptorCopyAttribute(font, att) let path = CFURLCopyFileSystemPath(test, 0) - + let buffer = Array.zeroCreate 8192 use ptr = fixed buffer CFStringGetCString(path, ptr, 8192, 0n) - + let mutable l = 0 while l < buffer.Length && buffer.[l] <> 0uy do l <- l + 1 - - + + System.Text.Encoding.UTF8.GetString(buffer, 0, l) - + let files = System.Collections.Generic.Dictionary>() let func = CFArrayCallbackDelegate(fun ptr _ -> let face = getString ptr NSFontFaceAttribute let family = getString ptr NSFontFamilyAttribute let path = getPath ptr NSCTFontFileURLAttribute - + match files.TryGetValue family with | (true, set) -> set.Add path |> ignore | _ -> let set = System.Collections.Generic.HashSet() set.Add path |> ignore files.[family] <- set - - + + ) CFArrayApplyFunction(arr, range, func, 0n) - - - + + + let allEntries = System.Collections.Generic.List() for KeyValue(family, files) in files do for f in files do - + let entries = FontTableEntries.ofFile f |> List.map (fun i -> { i with FamilyName = family }) allEntries.AddRange entries - - + + FontTable allEntries ) - - - - + + + + let tryLoadTypeFace (family : string) (weight : int) (italic : bool) : Option = try - let entry = + let entry = match Environment.OSVersion with | Windows -> Win32.table.Value.Find(family, weight, italic) |> Some diff --git a/src/Aardvark.Base.Fonts/Path.fs b/src/Aardvark.Base.Fonts/Path.fs index 02d7125b..1b71588a 100644 --- a/src/Aardvark.Base.Fonts/Path.fs +++ b/src/Aardvark.Base.Fonts/Path.fs @@ -1,9 +1,8 @@ -namespace Aardvark.Rendering.Text +namespace Aardvark.Base.Fonts open Aardvark.Base -open Aardvark.Rendering -[] +[] type WindingRule = | NonZero | Positive @@ -16,296 +15,6 @@ type Path = private { bounds : Box2d; outline : PathSegment[] } [] module Path = - module Attributes = - let KLMKind = Symbol.Create "KLMKind" - let ShapeTrafoR0 = Symbol.Create "ShapeTrafoR0" - let ShapeTrafoR1 = Symbol.Create "ShapeTrafoR1" - let PathColor = Symbol.Create "PathColor" - let TrafoOffsetAndScale = Symbol.Create "PathTrafoOffsetAndScale" - - type KLMKindAttribute() = inherit FShade.SemanticAttribute(Attributes.KLMKind |> string) - type ShapeTrafoR0Attribute() = inherit FShade.SemanticAttribute(Attributes.ShapeTrafoR0 |> string) - type ShapeTrafoR1Attribute() = inherit FShade.SemanticAttribute(Attributes.ShapeTrafoR1 |> string) - type PathColorAttribute() = inherit FShade.SemanticAttribute(Attributes.PathColor |> string) - type TrafoOffsetAndScaleAttribute() = inherit FShade.SemanticAttribute(Attributes.TrafoOffsetAndScale |> string) - type KindAttribute() = inherit FShade.SemanticAttribute("Kind") - type KLMAttribute() = inherit FShade.SemanticAttribute("KLM") - type DepthLayerAttribute() = inherit FShade.SemanticAttribute("DepthLayer") - - [] - module Shader = - open FShade - - type UniformScope with - member x.FillGlyphs : bool = uniform?FillGlyphs - member x.Antialias : bool = uniform?Antialias - member x.BoundaryColor : V4d = uniform?BoundaryColor - member x.DepthBias : float = uniform?DepthBias - - type Vertex = - { - [] p : V4d - [] klmKind : V4d - [] tr0 : V4d - [] tr1 : V4d - [] color : V4d - - [] samplePos : V2d - - [] layer : float - - - [] instanceTrafo : M34d - } - - type VertexNoSampleShading = - { - [] p : V4d - [] klmKind : V4d - [] tr0 : V4d - [] tr1 : V4d - [] color : V4d - - [] samplePos : V2d - - [] layer : float - - - [] instanceTrafo : M34d - } - - let eps = 0.00001 - [] - let keepsWinding (isOrtho : bool) (t : M44d) = - if isOrtho then - t.M00 > 0.0 - else - let c = V3d(t.M03, t.M13, t.M23) - let z = V3d(t.M02, t.M12, t.M22) - Vec.dot c z < 0.0 - - [] - let isOrtho (proj : M44d) = - abs proj.M30 < eps && - abs proj.M31 < eps && - abs proj.M32 < eps - - let pathVertex (v : Vertex) = - vertex { - let trafo = uniform.ModelViewTrafo - - let mutable p = V4d.Zero - - let flip = v.tr0.W < 0.0 - let pm = - V2d( - Vec.dot v.tr0.XYZ (V3d(v.p.XY, 1.0)), - Vec.dot v.tr1.XYZ (V3d(v.p.XY, 1.0)) - ) - - if flip then - if keepsWinding (isOrtho uniform.ProjTrafo) trafo then - p <- trafo * V4d( pm.X, pm.Y, v.p.Z, v.p.W) - else - p <- trafo * V4d(-pm.X, pm.Y, v.p.Z, v.p.W) - else - p <- trafo * V4d(pm.X, pm.Y, v.p.Z, v.p.W) - - return { - v with - p = uniform.ProjTrafo * p - //kind = v.klmKind.W - layer = 0.0 - //klm = v.klmKind.XYZ - color = v.color - } - } - - let pathVertexInstanced (v : Vertex) = - vertex { - let instanceTrafo = M44d.op_Explicit v.instanceTrafo //M44d.FromRows(v.instanceTrafo.R0, v.instanceTrafo.R1, v.instanceTrafo.R2, V4d.OOOI) - let trafo = uniform.ModelViewTrafo * instanceTrafo - - let flip = v.tr0.W < 0.0 - let pm = - V2d( - Vec.dot v.tr0.XYZ (V3d(v.p.XY, 1.0)), - Vec.dot v.tr1.XYZ (V3d(v.p.XY, 1.0)) - ) - - let mutable p = V4d.Zero - - if flip then - if keepsWinding (isOrtho uniform.ProjTrafo) trafo then - p <- trafo * V4d( pm.X, pm.Y, v.p.Z, v.p.W) - else - p <- trafo * V4d(-pm.X, pm.Y, v.p.Z, v.p.W) - else - p <- trafo * V4d(pm.X, pm.Y, v.p.Z, v.p.W) - - return { - v with - p = uniform.ProjTrafo * p - //kind = v.klmKind.W - layer = v.color.W - //klm = v.klmKind.XYZ - color = V4d(v.color.XYZ, 1.0) - } - } - - let pathVertexBillboard (v : Vertex) = - vertex { - let trafo = uniform.ModelViewTrafo - - let mvi = trafo.Transposed - let right = mvi.C0.XYZ |> Vec.normalize - let up = mvi.C1.XYZ |> Vec.normalize - - let pm = - V2d( - Vec.dot v.tr0.XYZ (V3d(v.p.XY, 1.0)), - Vec.dot v.tr1.XYZ (V3d(v.p.XY, 1.0)) - ) - - let mutable p = V4d.Zero - - - let pm = right * pm.X + up * pm.Y + V3d(0.0, 0.0, v.p.Z) - - p <- trafo * V4d(pm, 1.0) - - return { - v with - p = uniform.ProjTrafo * p - layer = 0.0 - color = v.color - } - } - - let pathVertexInstancedBillboard (v : Vertex) = - vertex { - let instanceTrafo = M44d.op_Explicit v.instanceTrafo - let trafo = uniform.ModelViewTrafo * instanceTrafo - - let mvi = trafo.Transposed - let right = mvi.C0.XYZ |> Vec.normalize - let up = mvi.C1.XYZ |> Vec.normalize - - - - let flip = v.tr0.W < 0.0 - let pm = - V2d( - Vec.dot v.tr0.XYZ (V3d(v.p.XY, 1.0)), - Vec.dot v.tr1.XYZ (V3d(v.p.XY, 1.0)) - ) - - let p = trafo * V4d(right * pm.X + up * pm.Y + V3d(0.0, 0.0, v.p.Z), v.p.W) - - return { - v with - p = uniform.ProjTrafo * p - layer = v.color.W - color = V4d(v.color.XYZ, 1.0) - } - } - - let depthBiasVs(v : Vertex) = - vertex { - let bias = 255.0 * v.layer * uniform.DepthBias - let p = v.p - V4d(0.0, 0.0, bias, 0.0) - return { v with p = p } - } - - let pathFragment(v : Vertex) = - fragment { - let kind = v.klmKind.W + 0.001 * v.samplePos.X - - let mutable color = v.color - - if uniform.FillGlyphs then - if kind > 1.5 && kind < 3.5 then - // bezier2 - let ci = v.klmKind.XYZ - let f = (ci.X * ci.X - ci.Y) * ci.Z - if f > 0.0 then - discard() - - elif kind > 3.5 && kind < 5.5 then - // arc - let ci = v.klmKind.XYZ - let f = ((ci.X * ci.X + ci.Y*ci.Y) - 1.0) * ci.Z - - if f > 0.0 then - discard() - - elif kind > 5.5 then - let ci = v.klmKind.XYZ - let f = ci.X * ci.X * ci.X - ci.Y * ci.Z - if f > 0.0 then - discard() - else - if kind > 1.5 && kind < 3.5 then - color <- V4d.IOOI - elif kind > 3.5 && kind < 5.5 then - color <- V4d.OIOI - elif kind > 5.5 then - color <- V4d.OOII - - return color - - } - - let pathFragmentNoSampleShading(v : VertexNoSampleShading) = - fragment { - let kind = v.klmKind.W - - let mutable color = v.color - - if uniform.FillGlyphs then - if kind > 1.5 && kind < 3.5 then - // bezier2 - let ci = v.klmKind.XYZ - let f = (ci.X * ci.X - ci.Y) * ci.Z - if f > 0.0 then - discard() - - elif kind > 3.5 && kind < 5.5 then - // arc - let ci = v.klmKind.XYZ - let f = ((ci.X * ci.X + ci.Y*ci.Y) - 1.0) * ci.Z - - if f > 0.0 then - discard() - - elif kind > 5.5 then - let ci = v.klmKind.XYZ - let f = ci.X * ci.X * ci.X - ci.Y * ci.Z - if f > 0.0 then - discard() - else - if kind > 1.5 && kind < 3.5 then - color <- V4d.IOOI - elif kind > 3.5 && kind < 5.5 then - color <- V4d.OIOI - elif kind > 5.5 then - color <- V4d.OOII - - return color - - } - - let boundaryVertex (v : Vertex) = - vertex { - return { v with p = uniform.ModelViewProjTrafo * v.p } - } - - let boundary (v : Vertex) = - fragment { - return uniform.BoundaryColor - } - - let empty = { bounds = Box2d.Invalid; outline = [||] } @@ -371,7 +80,7 @@ module Path = /// applies the given transformation to all points used by the path let transform (f : V2d -> V2d) (p : Path) = p.outline |> Array.map (PathSegment.transform f) |> ofArray - + type PathBuilderState = { currentStart : Option @@ -388,15 +97,15 @@ module Path = } [] - member x.Start(s : PathBuilderState, pt : V2d) = + member x.Start(s : PathBuilderState, pt : V2d) = { s with current = Some pt; currentStart = Some pt } - + [] - member x.LineTo(s : PathBuilderState, p1 : V2d) = + member x.LineTo(s : PathBuilderState, p1 : V2d) = match s.current with | Some p0 -> match PathSegment.tryLine p0 p1 with - | Some seg -> + | Some seg -> { s with current = Some p1; segments = seg :: s.segments } | None -> s @@ -404,62 +113,62 @@ module Path = failwith "cannot use lineTo without starting the path" [] - member x.BezierTo(s : PathBuilderState, pc : V2d, p1 : V2d) = + member x.BezierTo(s : PathBuilderState, pc : V2d, p1 : V2d) = match s.current with | Some p0 -> match PathSegment.tryBezier2 p0 pc p1 with - | Some seg -> + | Some seg -> { s with current = Some p1; segments = seg :: s.segments } | None -> s | None -> failwith "cannot use lineTo without starting the path" - + [] - member x.Arc(s : PathBuilderState, p1 : V2d, p2 : V2d) = + member x.Arc(s : PathBuilderState, p1 : V2d, p2 : V2d) = match s.current with | Some p0 -> match PathSegment.tryArcSegment p0 p1 p2 with - | Some seg -> + | Some seg -> { s with current = Some p2; segments = seg :: s.segments } | None -> s | None -> failwith "cannot use lineTo without starting the path" - + [] - member x.BezierTo3(s : PathBuilderState, pc0 : V2d, pc1 : V2d, p1 : V2d) = + member x.BezierTo3(s : PathBuilderState, pc0 : V2d, pc1 : V2d, p1 : V2d) = match s.current with | Some p0 -> match PathSegment.tryBezier3 p0 pc0 pc1 p1 with - | Some seg -> + | Some seg -> { s with current = Some p1; segments = seg :: s.segments } | None -> s | None -> failwith "cannot use lineTo without starting the path" - + [] member x.Stop(s : PathBuilderState) = { s with current = None; currentStart = None } - + [] member x.CloseLine(s : PathBuilderState) = match s.current, s.currentStart with | Some current, Some start -> - let s = { s with current = None; currentStart = None } - + let s = { s with current = None; currentStart = None } + match PathSegment.tryLine current start with | Some seg -> { s with segments = seg :: s.segments } | None -> s | _ -> failwith "cannot close without starting the path" - + member x.Run(s : PathBuilderState) = ofList (List.rev s.segments) let build = PathBuilder() - + /// creates a geometry using the !!closed!! path which contains the left-hand-side of /// the outline. /// The returned geometry contains Positions and a 4-dimensional vector (KLMKind) describing the @@ -474,5 +183,4 @@ module Path = | WindingRule.EvenOdd -> LibTessDotNet.Double.WindingRule.EvenOdd | WindingRule.AbsGreaterEqualTwo -> LibTessDotNet.Double.WindingRule.AbsGeqTwo - Tessellator.toGeometry rule p.bounds p.outline - + Tessellator.toGeometry rule p.bounds p.outline \ No newline at end of file diff --git a/src/Aardvark.Base.Fonts/PathSegment.fs b/src/Aardvark.Base.Fonts/PathSegment.fs index 717040e7..49567059 100644 --- a/src/Aardvark.Base.Fonts/PathSegment.fs +++ b/src/Aardvark.Base.Fonts/PathSegment.fs @@ -1,10 +1,9 @@ -namespace Aardvark.Rendering.Text - +namespace Aardvark.Base.Fonts open Aardvark.Base [] -module Helpers = +module Helpers = [] let internal epsilon = 1E-9 @@ -19,17 +18,17 @@ module Helpers = let v = p2 - p1 let uv2World = M33d.FromCols(V3d(u, 0.0), V3d(v, 0.0), V3d(p1, 1.0)) let world2UV = uv2World.Inverse - + let cuv = world2UV.TransformPos centerInWorld let ruv = Vec.length (V2d.IO - cuv) - + let r0 = Plane2d(Vec.normalize (V2d.IO - cuv), V2d.IO) let r2 = Plane2d(Vec.normalize (V2d.OI - cuv), V2d.OI) let mutable uv1 = V2d.Zero r0.Intersects(r2, &uv1) |> ignore (V2d.IO - cuv) / ruv, (uv1 - cuv) / ruv, (V2d.OI - cuv) / ruv - + let flip (a,b) = b,a let findBezierT (epsilon : float) (pt : V2d) (p0 : V2d) (p1 : V2d) (p2 : V2d) : option = @@ -52,10 +51,10 @@ module Helpers = let inline arcT (a0 : float) (da : float) (v : float) = let mutable a0 = a0 let mutable v = v - + while a0 < 0.0 do a0 <- a0 + Constant.PiTimesTwo while v < 0.0 do v <- v + Constant.PiTimesTwo - + if da < 0.0 then if v > a0 + 1E-8 then v <- v - Constant.PiTimesTwo (v - a0) / da @@ -111,7 +110,7 @@ module Helpers = let pc = n0.Intersect(n1) pc - + type PathSegment = private @@ -128,7 +127,7 @@ module internal PathSegmentIntersections = /// gets an axis aligned bounding box for the given segment let bounds (seg : PathSegment) = match seg with - | LineSeg(p0, p1) -> + | LineSeg(p0, p1) -> let mutable b = Box2d(p0, p0) b.ExtendBy p1 b @@ -143,7 +142,7 @@ module internal PathSegmentIntersections = b.ExtendBy p2 b.ExtendBy p3 b - | ArcSeg(p0, p1, a0, da, ellipse) -> + | ArcSeg(p0, p1, a0, da, ellipse) -> let mutable bb = Box2d(p0, p0) bb.ExtendBy p1 @@ -189,7 +188,7 @@ module internal PathSegmentIntersections = | Bezier2Seg(p0,_,_) -> p0 | Bezier3Seg(p0,_,_,_) -> p0 | ArcSeg(p0,_,_,_,_) -> p0 - + /// returns the end-point of the segment (t=1) let inline endPoint (seg : PathSegment) = match seg with @@ -204,7 +203,7 @@ module internal PathSegmentIntersections = elif t >= 1.0 then endPoint seg else match seg with - | LineSeg(p0, p1) -> + | LineSeg(p0, p1) -> lerp p0 p1 t | Bezier2Seg(p0, p1, p2) -> @@ -221,7 +220,7 @@ module internal PathSegmentIntersections = if t <= 0.0 then p0 elif t >= 1.0 then p1 else ellipse.GetPoint(a0 + t * da) - + /// evaluates the curve-derivative for the given parameter (t <- [0;1]) let derivative (t : float) (seg : PathSegment) = let t = clamp 0.0 1.0 t @@ -243,10 +242,10 @@ module internal PathSegmentIntersections = let v = p2 - p1 let w = p3 - p2 3.0 * (s*s*u + 2.0*t*s*v + t*t*w) - + [] - module private Implementations = + module private Implementations = let private teps = 1E-6 @@ -294,23 +293,23 @@ module internal PathSegmentIntersections = let q2 = b*b - 4.0*a*c - + let inline test (t : float) = if t >= -teps && t <= 1.0 + teps then let t = clamp 0.0 1.0 t - let pt = r.GetPointOnRay t + let pt = r.GetPointOnRay t match e.TryGetAlpha(pt, epsilon) with | Some a -> let ta = arcT alpha0 dAlpha a - if ta >= -teps && ta <= 1.0 + teps then + if ta >= -teps && ta <= 1.0 + teps then let ta = clamp 0.0 1.0 ta if Fun.ApproximateEquals(pt, e.GetPoint (alpha0 + ta*dAlpha), epsilon) then Some (ta, t) else None - else + else None - | None -> + | None -> None else None @@ -343,7 +342,7 @@ module internal PathSegmentIntersections = let bezier2Line (epsilon : float) (p0 : V2d) (p1 : V2d) (p2 : V2d) (q0 : V2d) (q1 : V2d) = // o+t*d = a*t^2 + b*t + c - + // o.X+s*d.X = a.X*t^2 + b.X*t + c.X // o.Y+s*d.Y = a.Y*t^2 + b.Y*t + c.Y @@ -396,38 +395,38 @@ module internal PathSegmentIntersections = let bezier2 (epsilon : float) (p0 : V2d) (p1 : V2d) (p2 : V2d) (q0 : V2d) (q1 : V2d) (q2 : V2d) = let f0 = - -4.0*q0.Y*q1.X*q1.Y*q2.X + 4.0*q0.X*sqr(q1.Y)*q2.X + sqr(q0.Y)*sqr(q2.X) + sqr(p0.Y)*sqr(q0.X - 2.0*q1.X + q2.X) + 4.0*q0.Y*sqr(q1.X)*q2.Y - 4.0*q0.X*q1.X*q1.Y*q2.Y - 2.0*q0.X*q0.Y*q2.X*q2.Y + sqr(q0.X)*sqr(q2.Y) + sqr(p0.X)*sqr(q0.Y - 2.0*q1.Y + q2.Y) - - 2.0*p0.Y*(-2.0*q0.X*q1.X*q1.Y + 4.0*q0.X*q1.Y*q2.X - 2.0*q1.X*q1.Y*q2.X + q0.Y*(2.0*sqr(q1.X) - q0.X*q2.X - 2.0*q1.X*q2.X + sqr(q2.X)) + sqr(q0.X)*q2.Y - 2.0*q0.X*q1.X*q2.Y + 2.0*sqr(q1.X)*q2.Y - q0.X*q2.X*q2.Y + p0.X*(q0.X - 2.0*q1.X + q2.X)*(q0.Y - 2.0*q1.Y + q2.Y)) + + -4.0*q0.Y*q1.X*q1.Y*q2.X + 4.0*q0.X*sqr(q1.Y)*q2.X + sqr(q0.Y)*sqr(q2.X) + sqr(p0.Y)*sqr(q0.X - 2.0*q1.X + q2.X) + 4.0*q0.Y*sqr(q1.X)*q2.Y - 4.0*q0.X*q1.X*q1.Y*q2.Y - 2.0*q0.X*q0.Y*q2.X*q2.Y + sqr(q0.X)*sqr(q2.Y) + sqr(p0.X)*sqr(q0.Y - 2.0*q1.Y + q2.Y) - + 2.0*p0.Y*(-2.0*q0.X*q1.X*q1.Y + 4.0*q0.X*q1.Y*q2.X - 2.0*q1.X*q1.Y*q2.X + q0.Y*(2.0*sqr(q1.X) - q0.X*q2.X - 2.0*q1.X*q2.X + sqr(q2.X)) + sqr(q0.X)*q2.Y - 2.0*q0.X*q1.X*q2.Y + 2.0*sqr(q1.X)*q2.Y - q0.X*q2.X*q2.Y + p0.X*(q0.X - 2.0*q1.X + q2.X)*(q0.Y - 2.0*q1.Y + q2.Y)) + 2.0*p0.X*(-(sqr(q0.Y)*q2.X) + 2.0*q1.Y*(-(q1.Y*q2.X) + q1.X*q2.Y) + q0.Y*(2.0*q1.Y*q2.X + 2.0*q1.X*(q1.Y - 2.0*q2.Y) + (q0.X + q2.X)*q2.Y) - q0.X*(2.0*sqr(q1.Y) - 2.0*q1.Y*q2.Y + sqr(q2.Y))) - + let f1 = - -4.0*(2.0*p1.Y*q0.Y*sqr(q1.X) - 2.0*p1.Y*q0.X*q1.X*q1.Y - 2.0*p1.X*q0.Y*q1.X*q1.Y + 2.0*p1.X*q0.X*sqr(q1.Y) - p1.Y*q0.X*q0.Y*q2.X + p1.X*sqr(q0.Y)*q2.X - 2.0*p1.Y*q0.Y*q1.X*q2.X + 4.0*p1.Y*q0.X*q1.Y*q2.X - 2.0*p1.X*q0.Y*q1.Y*q2.X - 2.0*p1.Y*q1.X*q1.Y*q2.X + 2.0*p1.X*sqr(q1.Y)*q2.X + - p1.Y*q0.Y*sqr(q2.X) + sqr(p0.Y)*sqr(q0.X - 2.0*q1.X + q2.X) + p1.Y*sqr(q0.X)*q2.Y - p1.X*q0.X*q0.Y*q2.Y - 2.0*p1.Y*q0.X*q1.X*q2.Y + 4.0*p1.X*q0.Y*q1.X*q2.Y + 2.0*p1.Y*sqr(q1.X)*q2.Y - 2.0*p1.X*q0.X*q1.Y*q2.Y - 2.0*p1.X*q1.X*q1.Y*q2.Y - p1.Y*q0.X*q2.X*q2.Y - p1.X*q0.Y*q2.X*q2.Y + - p1.X*q0.X*sqr(q2.Y) + sqr(p0.X)*sqr(q0.Y - 2.0*q1.Y + q2.Y) + p0.Y*(p1.X*q0.X*q0.Y - 2.0*p1.X*q0.Y*q1.X - 2.0*q0.Y*sqr(q1.X) - 2.0*p1.X*q0.X*q1.Y + 4.0*p1.X*q1.X*q1.Y + 2.0*q0.X*q1.X*q1.Y + p1.X*q0.Y*q2.X + q0.X*q0.Y*q2.X + 2.0*q0.Y*q1.X*q2.X - 2.0*p1.X*q1.Y*q2.X - 4.0*q0.X*q1.Y*q2.X + - 2.0*q1.X*q1.Y*q2.X - q0.Y*sqr(q2.X) - p1.Y*sqr(q0.X - 2.0*q1.X + q2.X) + p1.X*q0.X*q2.Y - sqr(q0.X)*q2.Y - 2.0*p1.X*q1.X*q2.Y + 2.0*q0.X*q1.X*q2.Y - 2.0*sqr(q1.X)*q2.Y + p1.X*q2.X*q2.Y + q0.X*q2.X*q2.Y - 2.0*p0.X*(q0.X - 2.0*q1.X + q2.X)*(q0.Y - 2.0*q1.Y + q2.Y)) + - p0.X*(2.0*q0.Y*q1.X*q1.Y - 2.0*q0.X*sqr(q1.Y) - sqr(q0.Y)*q2.X + 2.0*q0.Y*q1.Y*q2.X - 2.0*sqr(q1.Y)*q2.X + q0.X*q0.Y*q2.Y - 4.0*q0.Y*q1.X*q2.Y + 2.0*q0.X*q1.Y*q2.Y + 2.0*q1.X*q1.Y*q2.Y + q0.Y*q2.X*q2.Y - q0.X*sqr(q2.Y) + p1.Y*(q0.X - 2.0*q1.X + q2.X)*(q0.Y - 2.0*q1.Y + q2.Y) - + -4.0*(2.0*p1.Y*q0.Y*sqr(q1.X) - 2.0*p1.Y*q0.X*q1.X*q1.Y - 2.0*p1.X*q0.Y*q1.X*q1.Y + 2.0*p1.X*q0.X*sqr(q1.Y) - p1.Y*q0.X*q0.Y*q2.X + p1.X*sqr(q0.Y)*q2.X - 2.0*p1.Y*q0.Y*q1.X*q2.X + 4.0*p1.Y*q0.X*q1.Y*q2.X - 2.0*p1.X*q0.Y*q1.Y*q2.X - 2.0*p1.Y*q1.X*q1.Y*q2.X + 2.0*p1.X*sqr(q1.Y)*q2.X + + p1.Y*q0.Y*sqr(q2.X) + sqr(p0.Y)*sqr(q0.X - 2.0*q1.X + q2.X) + p1.Y*sqr(q0.X)*q2.Y - p1.X*q0.X*q0.Y*q2.Y - 2.0*p1.Y*q0.X*q1.X*q2.Y + 4.0*p1.X*q0.Y*q1.X*q2.Y + 2.0*p1.Y*sqr(q1.X)*q2.Y - 2.0*p1.X*q0.X*q1.Y*q2.Y - 2.0*p1.X*q1.X*q1.Y*q2.Y - p1.Y*q0.X*q2.X*q2.Y - p1.X*q0.Y*q2.X*q2.Y + + p1.X*q0.X*sqr(q2.Y) + sqr(p0.X)*sqr(q0.Y - 2.0*q1.Y + q2.Y) + p0.Y*(p1.X*q0.X*q0.Y - 2.0*p1.X*q0.Y*q1.X - 2.0*q0.Y*sqr(q1.X) - 2.0*p1.X*q0.X*q1.Y + 4.0*p1.X*q1.X*q1.Y + 2.0*q0.X*q1.X*q1.Y + p1.X*q0.Y*q2.X + q0.X*q0.Y*q2.X + 2.0*q0.Y*q1.X*q2.X - 2.0*p1.X*q1.Y*q2.X - 4.0*q0.X*q1.Y*q2.X + + 2.0*q1.X*q1.Y*q2.X - q0.Y*sqr(q2.X) - p1.Y*sqr(q0.X - 2.0*q1.X + q2.X) + p1.X*q0.X*q2.Y - sqr(q0.X)*q2.Y - 2.0*p1.X*q1.X*q2.Y + 2.0*q0.X*q1.X*q2.Y - 2.0*sqr(q1.X)*q2.Y + p1.X*q2.X*q2.Y + q0.X*q2.X*q2.Y - 2.0*p0.X*(q0.X - 2.0*q1.X + q2.X)*(q0.Y - 2.0*q1.Y + q2.Y)) + + p0.X*(2.0*q0.Y*q1.X*q1.Y - 2.0*q0.X*sqr(q1.Y) - sqr(q0.Y)*q2.X + 2.0*q0.Y*q1.Y*q2.X - 2.0*sqr(q1.Y)*q2.X + q0.X*q0.Y*q2.Y - 4.0*q0.Y*q1.X*q2.Y + 2.0*q0.X*q1.Y*q2.Y + 2.0*q1.X*q1.Y*q2.Y + q0.Y*q2.X*q2.Y - q0.X*sqr(q2.Y) + p1.Y*(q0.X - 2.0*q1.X + q2.X)*(q0.Y - 2.0*q1.Y + q2.Y) - p1.X*sqr(q0.Y - 2.0*q1.Y + q2.Y))) let f2 = - 2.0*(-(p0.X*p2.Y*q0.X*q0.Y) + 3.0*sqr(p0.X)*sqr(q0.Y) - 6.0*p0.X*p1.X*sqr(q0.Y) + 2.0*sqr(p1.X)*sqr(q0.Y) + p0.X*p2.X*sqr(q0.Y) + 2.0*p0.X*p2.Y*q0.Y*q1.X - 2.0*p2.Y*q0.Y*sqr(q1.X) + 2.0*p0.X*p2.Y*q0.X*q1.Y - 12.0*sqr(p0.X)*q0.Y*q1.Y + 24.0*p0.X*p1.X*q0.Y*q1.Y - - 8.0*sqr(p1.X)*q0.Y*q1.Y - 4.0*p0.X*p2.X*q0.Y*q1.Y - 4.0*p0.X*p2.Y*q1.X*q1.Y + 2.0*p2.Y*q0.X*q1.X*q1.Y + 2.0*p0.X*q0.Y*q1.X*q1.Y - 4.0*p1.X*q0.Y*q1.X*q1.Y + 2.0*p2.X*q0.Y*q1.X*q1.Y + 12.0*sqr(p0.X)*sqr(q1.Y) - 24.0*p0.X*p1.X*sqr(q1.Y) + 8.0*sqr(p1.X)*sqr(q1.Y) + - 4.0*p0.X*p2.X*sqr(q1.Y) - 2.0*p0.X*q0.X*sqr(q1.Y) + 4.0*p1.X*q0.X*sqr(q1.Y) - 2.0*p2.X*q0.X*sqr(q1.Y) - p0.X*p2.Y*q0.Y*q2.X + p2.Y*q0.X*q0.Y*q2.X - p0.X*sqr(q0.Y)*q2.X + 2.0*p1.X*sqr(q0.Y)*q2.X - p2.X*sqr(q0.Y)*q2.X + 2.0*p2.Y*q0.Y*q1.X*q2.X + 2.0*p0.X*p2.Y*q1.Y*q2.X - - 4.0*p2.Y*q0.X*q1.Y*q2.X + 2.0*p0.X*q0.Y*q1.Y*q2.X - 4.0*p1.X*q0.Y*q1.Y*q2.X + 2.0*p2.X*q0.Y*q1.Y*q2.X + 2.0*p2.Y*q1.X*q1.Y*q2.X - 2.0*p0.X*sqr(q1.Y)*q2.X + 4.0*p1.X*sqr(q1.Y)*q2.X - 2.0*p2.X*sqr(q1.Y)*q2.X - p2.Y*q0.Y*sqr(q2.X) + 3.0*sqr(p0.Y)*sqr(q0.X - 2.0*q1.X + q2.X) + - 2.0*sqr(p1.Y)*sqr(q0.X - 2.0*q1.X + q2.X) - p0.X*p2.Y*q0.X*q2.Y - p2.Y*sqr(q0.X)*q2.Y + 6.0*sqr(p0.X)*q0.Y*q2.Y - 12.0*p0.X*p1.X*q0.Y*q2.Y + 4.0*sqr(p1.X)*q0.Y*q2.Y + 2.0*p0.X*p2.X*q0.Y*q2.Y + p0.X*q0.X*q0.Y*q2.Y - 2.0*p1.X*q0.X*q0.Y*q2.Y + p2.X*q0.X*q0.Y*q2.Y + - 2.0*p0.X*p2.Y*q1.X*q2.Y + 2.0*p2.Y*q0.X*q1.X*q2.Y - 4.0*p0.X*q0.Y*q1.X*q2.Y + 8.0*p1.X*q0.Y*q1.X*q2.Y - 4.0*p2.X*q0.Y*q1.X*q2.Y - 2.0*p2.Y*sqr(q1.X)*q2.Y - 12.0*sqr(p0.X)*q1.Y*q2.Y + 24.0*p0.X*p1.X*q1.Y*q2.Y - 8.0*sqr(p1.X)*q1.Y*q2.Y - 4.0*p0.X*p2.X*q1.Y*q2.Y + 2.0*p0.X*q0.X*q1.Y*q2.Y - - 4.0*p1.X*q0.X*q1.Y*q2.Y + 2.0*p2.X*q0.X*q1.Y*q2.Y + 2.0*p0.X*q1.X*q1.Y*q2.Y - 4.0*p1.X*q1.X*q1.Y*q2.Y + 2.0*p2.X*q1.X*q1.Y*q2.Y - p0.X*p2.Y*q2.X*q2.Y + p2.Y*q0.X*q2.X*q2.Y + p0.X*q0.Y*q2.X*q2.Y - 2.0*p1.X*q0.Y*q2.X*q2.Y + p2.X*q0.Y*q2.X*q2.Y + 3.0*sqr(p0.X)*sqr(q2.Y) - 6.0*p0.X*p1.X*sqr(q2.Y) + - 2.0*sqr(p1.X)*sqr(q2.Y) + p0.X*p2.X*sqr(q2.Y) - p0.X*q0.X*sqr(q2.Y) + 2.0*p1.X*q0.X*sqr(q2.Y) - p2.X*q0.X*sqr(q2.Y) - - p0.Y*(6.0*p0.X*q0.X*q0.Y - 6.0*p1.X*q0.X*q0.Y + p2.X*q0.X*q0.Y - 12.0*p0.X*q0.Y*q1.X + 12.0*p1.X*q0.Y*q1.X - 2.0*p2.X*q0.Y*q1.X + 2.0*q0.Y*sqr(q1.X) - 12.0*p0.X*q0.X*q1.Y + 12.0*p1.X*q0.X*q1.Y - 2.0*p2.X*q0.X*q1.Y + 24.0*p0.X*q1.X*q1.Y - 24.0*p1.X*q1.X*q1.Y + 4.0*p2.X*q1.X*q1.Y - 2.0*q0.X*q1.X*q1.Y + - 6.0*p0.X*q0.Y*q2.X - 6.0*p1.X*q0.Y*q2.X + p2.X*q0.Y*q2.X - q0.X*q0.Y*q2.X - 2.0*q0.Y*q1.X*q2.X - 12.0*p0.X*q1.Y*q2.X + 12.0*p1.X*q1.Y*q2.X - 2.0*p2.X*q1.Y*q2.X + 4.0*q0.X*q1.Y*q2.X - 2.0*q1.X*q1.Y*q2.X + q0.Y*sqr(q2.X) + 6.0*p1.Y*sqr(q0.X - 2.0*q1.X + q2.X) - p2.Y*sqr(q0.X - 2.0*q1.X + q2.X) + - 6.0*p0.X*q0.X*q2.Y - 6.0*p1.X*q0.X*q2.Y + p2.X*q0.X*q2.Y + sqr(q0.X)*q2.Y - 12.0*p0.X*q1.X*q2.Y + 12.0*p1.X*q1.X*q2.Y - 2.0*p2.X*q1.X*q2.Y - 2.0*q0.X*q1.X*q2.Y + 2.0*sqr(q1.X)*q2.Y + 6.0*p0.X*q2.X*q2.Y - 6.0*p1.X*q2.X*q2.Y + p2.X*q2.X*q2.Y - q0.X*q2.X*q2.Y) + - 2.0*p1.Y*(2.0*q0.Y*sqr(q1.X) - 2.0*q0.X*q1.X*q1.Y - q0.X*q0.Y*q2.X - 2.0*q0.Y*q1.X*q2.X + 4.0*q0.X*q1.Y*q2.X - 2.0*q1.X*q1.Y*q2.X + q0.Y*sqr(q2.X) + sqr(q0.X)*q2.Y - 2.0*q0.X*q1.X*q2.Y + 2.0*sqr(q1.X)*q2.Y - q0.X*q2.X*q2.Y + 3.0*p0.X*(q0.X - 2.0*q1.X + q2.X)*(q0.Y - 2.0*q1.Y + q2.Y) - + 2.0*(-(p0.X*p2.Y*q0.X*q0.Y) + 3.0*sqr(p0.X)*sqr(q0.Y) - 6.0*p0.X*p1.X*sqr(q0.Y) + 2.0*sqr(p1.X)*sqr(q0.Y) + p0.X*p2.X*sqr(q0.Y) + 2.0*p0.X*p2.Y*q0.Y*q1.X - 2.0*p2.Y*q0.Y*sqr(q1.X) + 2.0*p0.X*p2.Y*q0.X*q1.Y - 12.0*sqr(p0.X)*q0.Y*q1.Y + 24.0*p0.X*p1.X*q0.Y*q1.Y - + 8.0*sqr(p1.X)*q0.Y*q1.Y - 4.0*p0.X*p2.X*q0.Y*q1.Y - 4.0*p0.X*p2.Y*q1.X*q1.Y + 2.0*p2.Y*q0.X*q1.X*q1.Y + 2.0*p0.X*q0.Y*q1.X*q1.Y - 4.0*p1.X*q0.Y*q1.X*q1.Y + 2.0*p2.X*q0.Y*q1.X*q1.Y + 12.0*sqr(p0.X)*sqr(q1.Y) - 24.0*p0.X*p1.X*sqr(q1.Y) + 8.0*sqr(p1.X)*sqr(q1.Y) + + 4.0*p0.X*p2.X*sqr(q1.Y) - 2.0*p0.X*q0.X*sqr(q1.Y) + 4.0*p1.X*q0.X*sqr(q1.Y) - 2.0*p2.X*q0.X*sqr(q1.Y) - p0.X*p2.Y*q0.Y*q2.X + p2.Y*q0.X*q0.Y*q2.X - p0.X*sqr(q0.Y)*q2.X + 2.0*p1.X*sqr(q0.Y)*q2.X - p2.X*sqr(q0.Y)*q2.X + 2.0*p2.Y*q0.Y*q1.X*q2.X + 2.0*p0.X*p2.Y*q1.Y*q2.X - + 4.0*p2.Y*q0.X*q1.Y*q2.X + 2.0*p0.X*q0.Y*q1.Y*q2.X - 4.0*p1.X*q0.Y*q1.Y*q2.X + 2.0*p2.X*q0.Y*q1.Y*q2.X + 2.0*p2.Y*q1.X*q1.Y*q2.X - 2.0*p0.X*sqr(q1.Y)*q2.X + 4.0*p1.X*sqr(q1.Y)*q2.X - 2.0*p2.X*sqr(q1.Y)*q2.X - p2.Y*q0.Y*sqr(q2.X) + 3.0*sqr(p0.Y)*sqr(q0.X - 2.0*q1.X + q2.X) + + 2.0*sqr(p1.Y)*sqr(q0.X - 2.0*q1.X + q2.X) - p0.X*p2.Y*q0.X*q2.Y - p2.Y*sqr(q0.X)*q2.Y + 6.0*sqr(p0.X)*q0.Y*q2.Y - 12.0*p0.X*p1.X*q0.Y*q2.Y + 4.0*sqr(p1.X)*q0.Y*q2.Y + 2.0*p0.X*p2.X*q0.Y*q2.Y + p0.X*q0.X*q0.Y*q2.Y - 2.0*p1.X*q0.X*q0.Y*q2.Y + p2.X*q0.X*q0.Y*q2.Y + + 2.0*p0.X*p2.Y*q1.X*q2.Y + 2.0*p2.Y*q0.X*q1.X*q2.Y - 4.0*p0.X*q0.Y*q1.X*q2.Y + 8.0*p1.X*q0.Y*q1.X*q2.Y - 4.0*p2.X*q0.Y*q1.X*q2.Y - 2.0*p2.Y*sqr(q1.X)*q2.Y - 12.0*sqr(p0.X)*q1.Y*q2.Y + 24.0*p0.X*p1.X*q1.Y*q2.Y - 8.0*sqr(p1.X)*q1.Y*q2.Y - 4.0*p0.X*p2.X*q1.Y*q2.Y + 2.0*p0.X*q0.X*q1.Y*q2.Y - + 4.0*p1.X*q0.X*q1.Y*q2.Y + 2.0*p2.X*q0.X*q1.Y*q2.Y + 2.0*p0.X*q1.X*q1.Y*q2.Y - 4.0*p1.X*q1.X*q1.Y*q2.Y + 2.0*p2.X*q1.X*q1.Y*q2.Y - p0.X*p2.Y*q2.X*q2.Y + p2.Y*q0.X*q2.X*q2.Y + p0.X*q0.Y*q2.X*q2.Y - 2.0*p1.X*q0.Y*q2.X*q2.Y + p2.X*q0.Y*q2.X*q2.Y + 3.0*sqr(p0.X)*sqr(q2.Y) - 6.0*p0.X*p1.X*sqr(q2.Y) + + 2.0*sqr(p1.X)*sqr(q2.Y) + p0.X*p2.X*sqr(q2.Y) - p0.X*q0.X*sqr(q2.Y) + 2.0*p1.X*q0.X*sqr(q2.Y) - p2.X*q0.X*sqr(q2.Y) - + p0.Y*(6.0*p0.X*q0.X*q0.Y - 6.0*p1.X*q0.X*q0.Y + p2.X*q0.X*q0.Y - 12.0*p0.X*q0.Y*q1.X + 12.0*p1.X*q0.Y*q1.X - 2.0*p2.X*q0.Y*q1.X + 2.0*q0.Y*sqr(q1.X) - 12.0*p0.X*q0.X*q1.Y + 12.0*p1.X*q0.X*q1.Y - 2.0*p2.X*q0.X*q1.Y + 24.0*p0.X*q1.X*q1.Y - 24.0*p1.X*q1.X*q1.Y + 4.0*p2.X*q1.X*q1.Y - 2.0*q0.X*q1.X*q1.Y + + 6.0*p0.X*q0.Y*q2.X - 6.0*p1.X*q0.Y*q2.X + p2.X*q0.Y*q2.X - q0.X*q0.Y*q2.X - 2.0*q0.Y*q1.X*q2.X - 12.0*p0.X*q1.Y*q2.X + 12.0*p1.X*q1.Y*q2.X - 2.0*p2.X*q1.Y*q2.X + 4.0*q0.X*q1.Y*q2.X - 2.0*q1.X*q1.Y*q2.X + q0.Y*sqr(q2.X) + 6.0*p1.Y*sqr(q0.X - 2.0*q1.X + q2.X) - p2.Y*sqr(q0.X - 2.0*q1.X + q2.X) + + 6.0*p0.X*q0.X*q2.Y - 6.0*p1.X*q0.X*q2.Y + p2.X*q0.X*q2.Y + sqr(q0.X)*q2.Y - 12.0*p0.X*q1.X*q2.Y + 12.0*p1.X*q1.X*q2.Y - 2.0*p2.X*q1.X*q2.Y - 2.0*q0.X*q1.X*q2.Y + 2.0*sqr(q1.X)*q2.Y + 6.0*p0.X*q2.X*q2.Y - 6.0*p1.X*q2.X*q2.Y + p2.X*q2.X*q2.Y - q0.X*q2.X*q2.Y) + + 2.0*p1.Y*(2.0*q0.Y*sqr(q1.X) - 2.0*q0.X*q1.X*q1.Y - q0.X*q0.Y*q2.X - 2.0*q0.Y*q1.X*q2.X + 4.0*q0.X*q1.Y*q2.X - 2.0*q1.X*q1.Y*q2.X + q0.Y*sqr(q2.X) + sqr(q0.X)*q2.Y - 2.0*q0.X*q1.X*q2.Y + 2.0*sqr(q1.X)*q2.Y - q0.X*q2.X*q2.Y + 3.0*p0.X*(q0.X - 2.0*q1.X + q2.X)*(q0.Y - 2.0*q1.Y + q2.Y) - 2.0*p1.X*(q0.X - 2.0*q1.X + q2.X)*(q0.Y - 2.0*q1.Y + q2.Y))) let f3 = -4.0*(p2.Y*q0.X - p0.X*q0.Y + 2.0*p1.X*q0.Y - p2.X*q0.Y - 2.0*p2.Y*q1.X + 2.0*p0.X*q1.Y - 4.0*p1.X*q1.Y + 2.0*p2.X*q1.Y + p2.Y*q2.X + p0.Y*(q0.X - 2.0*q1.X + q2.X) - 2.0*p1.Y*(q0.X - 2.0*q1.X + q2.X) - p0.X*q2.Y + 2.0*p1.X*q2.Y - p2.X*q2.Y)* (p0.Y*(q0.X - 2.0*q1.X + q2.X) - p1.Y*(q0.X - 2.0*q1.X + q2.X) - (p0.X - p1.X)*(q0.Y - 2.0*q1.Y + q2.Y)) - - let f4 = + + let f4 = sqr(-(p2.Y*q0.X) + p0.X*q0.Y - 2.0*p1.X*q0.Y + p2.X*q0.Y + 2.0*p2.Y*q1.X - 2.0*p0.X*q1.Y + 4.0*p1.X*q1.Y - 2.0*p2.X*q1.Y - p2.Y*q2.X - p0.Y*(q0.X - 2.0*q1.X + q2.X) + 2.0*p1.Y*(q0.X - 2.0*q1.X + q2.X) + p0.X*q2.Y - 2.0*p1.X*q2.Y + p2.X*q2.Y) let struct (t0, t1, t2, t3) = Polynomial.RealRootsOf(f4, f3, f2, f1, f0) @@ -447,7 +446,7 @@ module internal PathSegmentIntersections = Some (t, q0*s*s + 2.0*q1*s*t + q2*t*t) else None - + let test (tp : float) = match evalP tp with | Some(tp, pp) -> @@ -478,8 +477,8 @@ module internal PathSegmentIntersections = // |c + cos t * a + sin t * b|^2 = 1 // = 1 - // + cos(t)* + sin(t)* + - // cos(t)* + cos(t)^2* + sin(t)*cos(t)* + + // + cos(t)* + sin(t)* + + // cos(t)* + cos(t)^2* + sin(t)*cos(t)* + // sin(t)* + sin(t)*cos(t)* + sin(t)^2* - 1 = 0 // (cos(t)^2* + sin(t)^2*) + 2*(sin(t)*cos(t)* + cos(t)* + sin(t)*) + - 1 = 0 @@ -513,7 +512,7 @@ module internal PathSegmentIntersections = let g4 = 4.0*sqr(ab) + sqr(aa - bb) let struct(s0, s1, s2, s3) = Polynomial.RealRootsOf(g4, g3, g2, g1, g0) - + let add (v0 : float, v1 : float) (l : list) = let exists = l |> List.exists (fun (va, vb) -> Fun.ApproximateEquals(v0, va, 1E-8) && Fun.ApproximateEquals(v1, vb, 1E-8)) if exists then l @@ -522,12 +521,12 @@ module internal PathSegmentIntersections = let sols = let mutable sols = [] for c in [c0;c1;c2;c3] do - if c >= -1.0 && c <= 1.0 then + if c >= -1.0 && c <= 1.0 then let s = sqrt(1.0 - sqr c) sols <- sols |> add (c, s) |> add (c, -s) - + for s in [s0;s1;s2;s3] do - if s >= -1.0 && s <= 1.0 then + if s >= -1.0 && s <= 1.0 then let c = sqrt(1.0 - sqr s) sols <- sols |> add (c, s) |> add (-c, s) @@ -548,7 +547,7 @@ module internal PathSegmentIntersections = else getSolutions acc t - + @@ -573,7 +572,7 @@ module internal PathSegmentIntersections = else None ) - + let private bezier2Ellipse (p0 : V2d) (p1 : V2d) (p2 : V2d) (e : Ellipse2d) = let m = M33d.FromCols(V3d(e.Axis0, 0.0), V3d(e.Axis1, 0.0), V3d(e.Center, 1.0)) let mi = m.Inverse @@ -588,7 +587,7 @@ module internal PathSegmentIntersections = // |a*t^2 + b*t + c| = 1 // = 1 - + // t^4* + t^3* + t^2* + // t^3* + t^2* + t* + // t^2* + t* + - 1 = 0 @@ -630,9 +629,9 @@ module internal PathSegmentIntersections = else None ) - + let bezier3Line (epsilon : float) (p0 : V2d) (p1 : V2d) (p2 : V2d) (p3 : V2d) (q0 : V2d) (q1 : V2d) = - + let a = -p0 + 3.0*p1 - 3.0*p2 + p3 let b = 3.0*p0 - 6.0*p1 + 3.0*p2 let c = 3.0*p1 - 3.0*p0 @@ -646,7 +645,7 @@ module internal PathSegmentIntersections = // v.X*t + o.X = a.X*s^3 + b.X*s^2 + c.X*s + d.X // v.Y*t + o.Y = a.Y*s^3 + b.Y*s^2 + c.Y*s + d.Y - + // v.Y*v.X*t + v.Y*o.X = v.Y*a.X*s^3 + v.Y*b.X*s^2 + v.Y*c.X*s + v.Y*d.X // -v.X*v.Y*t - v.X*o.Y = -v.X*a.Y*s^3 - v.X*b.Y*s^2 - v.X*c.Y*s - v.X*d.Y @@ -705,7 +704,7 @@ module internal PathSegmentIntersections = Bezier3Seg(p0, q0, m0, c), Bezier3Seg(c, m1, q2, p3) let numeric (epsilon : float) (l : PathSegment) (r : PathSegment) = - + let stack = System.Collections.Generic.Stack(64) let mutable result = [] @@ -745,8 +744,8 @@ module internal PathSegmentIntersections = t <- t + dt lastStep <- dt.NormMax iter <- iter + 1 - - + + let tl = clamp 0.0 1.0 t.X let tr = clamp 0.0 1.0 t.Y @@ -758,7 +757,7 @@ module internal PathSegmentIntersections = if Fun.ApproximateEquals(pl, pr, largeEps) then let pt = (pl + pr) / 2.0 let contained = points |> List.exists (fun p -> Fun.ApproximateEquals(p, pt, largeEps)) - if not contained then + if not contained then result <- (tl, tr) :: result points <- pt :: points cnt <- cnt + 1 @@ -782,7 +781,7 @@ module internal PathSegmentIntersections = else let l0, l1 = halves l let r0, r1 = halves r - + let lsh = ls / 2.0 let rsh = rs / 2.0 @@ -806,10 +805,10 @@ module internal PathSegmentIntersections = lines eps a0 a1 b0 b1 | LineSeg(a0, a1), Bezier2Seg(b0, b1, b2) -> - bezier2Line eps b0 b1 b2 a0 a1 + bezier2Line eps b0 b1 b2 a0 a1 |> List.map flip |> List.sortBy fst - + | LineSeg(a0, a1), ArcSeg(c0, c1, b0, db, b) -> arcLine eps c0 c1 b0 db b a0 a1 |> List.map flip @@ -823,7 +822,7 @@ module internal PathSegmentIntersections = | Bezier2Seg(a0, a1, a2), LineSeg(b0, b1) -> bezier2Line eps a0 a1 a2 b0 b1 |> List.sortBy fst - + | Bezier2Seg(a0, a1, a2), Bezier2Seg(b0, b1, b2) -> bezier2 eps a0 a1 a2 b0 b1 b2 |> List.sortBy fst @@ -831,7 +830,7 @@ module internal PathSegmentIntersections = | Bezier2Seg(a0, a1, a2), ArcSeg(_, _, b0, db, b) -> bezier2Arc eps a0 a1 a2 b0 db b |> List.sortBy fst - + | Bezier2Seg _, Bezier3Seg _ -> numeric eps a b |> List.sortBy fst @@ -839,12 +838,12 @@ module internal PathSegmentIntersections = | ArcSeg(c0, c1, a0, da, a), LineSeg(b0, b1) -> arcLine eps c0 c1 a0 da a b0 b1 |> List.sortBy fst - + | ArcSeg(_, _, a0, da, a), Bezier2Seg(b0, b1, b2) -> bezier2Arc eps b0 b1 b2 a0 da a |> List.map flip |> List.sortBy fst - + | ArcSeg(ap0, ap1, a0, da, a), ArcSeg(bp0, bp1, b0, db, b) -> if Fun.ApproximateEquals(ap0, bp0, eps) then [0.0, 0.0] elif Fun.ApproximateEquals(ap0, bp1, eps) then [0.0, 1.0] @@ -853,7 +852,7 @@ module internal PathSegmentIntersections = else arcs eps a0 da a b0 db b |> List.sortBy fst - + | ArcSeg _, Bezier3Seg _ -> numeric eps a b |> List.sortBy fst @@ -861,7 +860,7 @@ module internal PathSegmentIntersections = | Bezier3Seg(a0, a1, a2, a3), LineSeg(b0, b1) -> bezier3Line eps a0 a1 a2 a3 b0 b1 |> List.sortBy fst - + | Bezier3Seg _, Bezier2Seg _ -> numeric eps a b |> List.sortBy fst @@ -873,11 +872,11 @@ module internal PathSegmentIntersections = | Bezier3Seg _, Bezier3Seg _ -> numeric eps a b |> List.sortBy fst - + [] module PathSegment = - + let inline private cc (a : V2d) (b : V2d) = a.X*b.Y - a.Y*b.X @@ -915,25 +914,25 @@ module PathSegment = | Bezier2Seg(p0, p1, p2) -> PathSegment.Bezier2Seg(p0, p1, pt) | Bezier3Seg(p0, p1, p2, p3) -> PathSegment.Bezier3Seg(p0, p1, p2, pt) | ArcSeg(p0, p1, a, da, e) -> PathSegment.ArcSeg(p0, pt, a, da, e) - + /// creates a line segment - let line (p0 : V2d) (p1 : V2d) = + let line (p0 : V2d) (p1 : V2d) = if Fun.ApproximateEquals(p0, p1, epsilon) then failwithf "[PathSegment] degenerate line at: %A" p0 LineSeg(p0, p1) /// creates a quadratic bezier segment - let bezier2 (p0 : V2d) (p1 : V2d) (p2 : V2d) = + let bezier2 (p0 : V2d) (p1 : V2d) (p2 : V2d) = // check if the spline is actually a line let d = p2 - p0 |> Vec.normalize let n = V2d(-d.Y, d.X) - - - if Fun.IsTiny(Vec.dot (p1 - p0) n, epsilon) then + + + if Fun.IsTiny(Vec.dot (p1 - p0) n, epsilon) then line p0 p2 - else + else Bezier2Seg(p0, p1, p2) /// creates a cubic bezier segment @@ -958,7 +957,7 @@ module PathSegment = bezier2 p0 ctrl p3 else Bezier3Seg(p0, p1, p2, p3) - + /// creates a line segment (if not degenerate) @@ -967,15 +966,15 @@ module PathSegment = else Some (LineSeg(p0, p1)) /// creates a qudratic bezier segment (if not degenerate) - let tryBezier2 (p0 : V2d) (p1 : V2d) (p2 : V2d) = + let tryBezier2 (p0 : V2d) (p1 : V2d) (p2 : V2d) = // check if the spline is actually a line let d = p2 - p0 |> Vec.normalize let n = V2d(-d.Y, d.X) - - - if Fun.IsTiny(Vec.dot (p1 - p0) n, epsilon) then + + + if Fun.IsTiny(Vec.dot (p1 - p0) n, epsilon) then tryLine p0 p2 - else + else Bezier2Seg(p0, p1, p2) |> Some /// creates a cubic bezier segment (if not degenerate) @@ -986,33 +985,33 @@ module PathSegment = let dd = (p3 - p0) let len = Vec.length dd let d03 = dd / len - + let n = V2d(-d03.Y, d03.X) let h1 = Vec.dot (p1 - p0) n let h2 = Vec.dot (p2 - p0) n if Fun.IsTiny(h1, epsilon) && Fun.IsTiny(h2, epsilon) then tryLine p0 p3 - else + else // let f3 = -p0 + 3.0*p1 - 3.0*p2 + p3 // let f2 = 3.0*p0 - 6.0*p1 + 3.0*p2 // let f1 = 3.0*p1 - 3.0*p0 // let f0 = p0 - - + + // let g2 = q2 + q0 - 2.0*q1 // let g1 = 2.0*(q1 - q0) // let g0 = q0 - - + + // f3 = 0 // => // q2 + q0 - 2.0*q1 = 3.0*p0 - 6.0*p1 + 3.0*p2 // 3.0*p1 - 3.0*p0 = 2.0*q1 - 2.0*q0 // p0 = q0 - + // semantically: q2 = p3 - + // q1 = -p0 + 3*p1 - 1.5*p2 + 0.5*p3 let f3 = -p0 + 3.0*p1 - 3.0*p2 + p3 if Fun.IsTiny(f3, epsilon) then @@ -1034,7 +1033,7 @@ module PathSegment = else ArcSeg(p0, p1, a0, da, newEllipse) |> Some - + /// creates an arc using the an angle alpha0 and a (signed) dAlpha. /// in order to avoid precision issues p0,p1 users may redundantly supply p0 and p1. /// the implementation validates that p0~ellipse.GetPoint(alpha0) and p1~ellipse.GetPoint(alpha0+dAlpha) respectively. @@ -1045,7 +1044,7 @@ module PathSegment = createArc false p0 p1 alpha0 dAlpha ellipse else None - + /// creates an arc using the an angle alpha0 and a (signed) dAlpha. /// in order to avoid precision issues p0,p1 users may redundantly supply p0 and p1. /// the implementation validates that p0~ellipse.GetPoint(alpha0) and p1~ellipse.GetPoint(alpha0+dAlpha) respectively. @@ -1063,22 +1062,22 @@ module PathSegment = let p0 = ellipse.GetPoint alpha0 let p1 = ellipse.GetPoint (alpha0 + dAlpha) createArc false p0 p1 alpha0 dAlpha ellipse - + /// creates an arc using the an angle alpha0 and a (signed) dAlpha let arc (alpha0 : float) (dAlpha : float) (ellipse : Ellipse2d) = tryArc alpha0 dAlpha ellipse |> Option.get - /// creates an arc-segment passing through p0 and p2. + /// creates an arc-segment passing through p0 and p2. /// the tangents are made parallel to (p1-p0) and (p2-p1) respectively. /// since this configuration is ambigous the ellipse with minimal eccentricity is chosen. let tryArcSegment (p0 : V2d) (p1 : V2d) (p2 : V2d) = // check if the arc is actually a line let d = p2 - p0 |> Vec.normalize let n = V2d(-d.Y, d.X) - - if Fun.IsTiny(Vec.dot (p1 - p0) n, epsilon) then + + if Fun.IsTiny(Vec.dot (p1 - p0) n, epsilon) then tryLine p0 p2 - else + else // see: https://math.stackexchange.com/questions/2204258/roundest-ellipse-with-specified-tangents let m = 0.5 * (p0 + p2) let am = m - p0 @@ -1094,7 +1093,7 @@ module PathSegment = let d = m + vm * (lmd / lvm) let inline ln (v : V2d) = V2d(-v.Y, v.X) - + let pd = Plane2d(ln (am / lam), d) let pv = Plane2d(ln (va / lva), p0) let mutable e = V2d.Zero @@ -1109,7 +1108,7 @@ module PathSegment = if pef.Intersects(pvm, &c) then let lcd = Vec.length (d - c) let g = c - am * (lcd/lam) - + let ellipse = createEllipse c (d - c) (g - c) let a0 = ellipse.GetAlpha p0 let a1 = ellipse.GetAlpha p2 @@ -1123,8 +1122,8 @@ module PathSegment = else tryLine p0 p2 - - /// creates an arc-segment passing through p0 and p2. + + /// creates an arc-segment passing through p0 and p2. /// the tangents are made parallel to (p1-p0) and (p2-p1) respectively. /// since this configuration is ambigous the ellipse with minimal eccentricity is chosen. let arcSegment (p0 : V2d) (p1 : V2d) (p2 : V2d) = @@ -1138,7 +1137,7 @@ module PathSegment = | Bezier2Seg(p0,_,_) -> p0 | Bezier3Seg(p0,_,_,_) -> p0 | ArcSeg(p0,_,_,_,_) -> p0 - + /// returns the end-point of the segment (t=1) let endPoint (seg : PathSegment) = match seg with @@ -1153,7 +1152,7 @@ module PathSegment = elif t >= 1.0 then endPoint seg else match seg with - | LineSeg(p0, p1) -> + | LineSeg(p0, p1) -> lerp p0 p1 t | Bezier2Seg(p0, p1, p2) -> @@ -1170,7 +1169,7 @@ module PathSegment = if t <= 0.0 then p0 elif t >= 1.0 then p1 else ellipse.GetPoint(a0 + t * da) - + /// evaluates the curve-derivative for the given parameter (t <- [0;1]) let derivative (t : float) (seg : PathSegment) = let t = clamp 0.0 1.0 t @@ -1192,7 +1191,7 @@ module PathSegment = let v = p2 - p1 let w = p3 - p2 3.0 * (s*s*u + 2.0*t*s*v + t*t*w) - + /// evaluates the second curve-derivative for the given parameter (t <- [0;1]) let secondDerivative (t : float) (seg : PathSegment) = let t = clamp 0.0 1.0 t @@ -1212,7 +1211,7 @@ module PathSegment = let v = p2 - p1 let w = p3 - p2 6.0 * ((1.0-t)*(v-u) + t*(w-v)) - + /// evaluates the third curve-derivative for the given parameter (t <- [0;1]) let thirdDerivative (t : float) (seg : PathSegment) = let t = clamp 0.0 1.0 t @@ -1232,192 +1231,192 @@ module PathSegment = let v = p2 - p1 let w = p3 - p2 6.0 * (u+w - 2.0*v) - + /// evaluates the curvature for the given parameter (t <- [0;1]). /// the curvature is defined as: c(t) := (x'(t)*y''(t) - y'(t)*x''(t)) / (x'(t)^2 + y'(t)^2)^(3/2). /// see https://en.wikipedia.org/wiki/Curvature#In_terms_of_a_general_parametrization let curvature (t : float) (seg : PathSegment) = match seg with - | LineSeg _ -> + | LineSeg _ -> 0.0 | _ -> - - + + //(df.X*ddf.Y - df.Y*ddf.X) / (df.LengthSquared ** 1.5) - - + + // f = t^2 * (p0 - 2*p1 + p2) + t*2*(p1 - p0) + p0 - - + + // df = 2.0 * ((1-t)*(p1 - p0) + t*(p2 - p1)) // ddf = 2.0 * (p0 - 2.0*p1 + p2) - - + + // df/2 =(p1 - p0)- t*(p1 - p0) + t*(p2 - p1) - - + + // df/2 = (p1 - p0) - t*(p1 - p0) + t*(p2 - p1) // df = t * 2*(p2 - 2*p1 + p0) + 2*(p1 - p0) // ddf = 2*(p2 - 2*p1 + p0) - - + + // c = (df.X*ddf.Y - df.Y*ddf.X) / (df.LengthSquared ** 1.5) // c^2 = max - + // c^2 = (df.X*ddf.Y - df.Y*ddf.X)^2 / (df.LengthSquared ** 3) - + // a := 2*(p2 - 2*p1 + p0) // b := 2*(p1 - p0) - - + + // df = t*a + b // ddf = a - + // ((t*a.X + b.X)*a.Y - (t*a.Y + b.Y)*a.X)^2 / ((t*a + b)^2)^3 - + // (t*a.X*a.Y + b.X*a.Y - t*a.Y*a.X - b.Y*a.X)^2 / ((t*a + b)^2)^3 // c^2 = (b.X*a.Y - b.Y*a.X)^2 * ((t*a + b)^2)^-3 - + // dc^2 / dt = -3 * (b.X*a.Y - b.Y*a.X)^2 * ((t*a + b)^2)^-4 * 2*(t*a + b) * a - - + + // (t*a + b) * a = 0 // t = - / - - - + + + // d := t0 // a := 2.0*(p0 - 2*p1 + p2) - + // df = a*t + d // ddf = a - + // c^2 = [(a.X*t+d.X)*a.Y - (a.Y*t+d.Y)*a.X]^2 * [(a*t+d)^2]^-3 // c^2 = [t*(a.X*a.Y - a.Y*a.X) + (d.X*a.Y - d.Y*a.X)]^2 * [(a*t+d)^2]^-3 - + // f := a.X*a.Y - a.Y*a.X // g := d.X*a.Y - d.Y*a.X - + // f = 0 - + // c^2 = [t*f + g]^2 * [(a*t+d)^2]^-3 - - + + // c^2 = g^2 * [(a*t+d)^2]^-3 - + // dc^2 / dt = -3*g^2 * [(a*t+d)^2]^-4 - - + + // dc^2 / dt = 2*[t*f + g]*f * [(a*t+d)^2]^-3 - 6*[t*f + g]^2 * (a*t+d) * [(a*t+d)^2]^-4 - + // [t*f + g]*f * [(a*t+d)^2]^-3 - 3*[t*f + g]^2 * (a*t+d) * [(a*t+d)^2]^-4 = 0 /// * [(a*t+d)^2]^4 - - // [t*f + g]*f * [(a*t+d)^2] - 3*[t*f + g]^2 * (a*t+d) = 0 - - - - - - - + + // [t*f + g]*f * [(a*t+d)^2] - 3*[t*f + g]^2 * (a*t+d) = 0 + + + + + + + let df = derivative t seg - let ddf = secondDerivative t seg + let ddf = secondDerivative t seg (df.X*ddf.Y - df.Y*ddf.X) / (df.LengthSquared ** 1.5) - + /// evaluates the curvature-derivative for the given parameter (t <- [0;1]). let curvatureDerivative (t : float) (seg : PathSegment) = match seg with - | LineSeg _ -> + | LineSeg _ -> 0.0 | _ -> - + // F = df/dt(t) // G = d2f/dt^2(t) - + // (F.X*G.Y - F.Y*G.X) * (F.X*G.X + F.Y*G.Y) - + // sqr F.X*G.X*G.Y - sqr F.Y*G.X*G.Y - F.X*F.Y*sqr G.X + F.X*F.Y*sqr G.Y - + // G.X*G.Y*(sqr F.X - sqr F.Y) + F.X*F.Y*(sqr G.Y - sqr G.X) - + // (Fx*Gy - Fy*Gx) * (Fx^2 + Fy^2)^(2/3) - + // (Gx*Gy + Fx*Hy - Gy*Gx - Fy*Hx) * (Fx^2 + Fy^2)^(2/3) + // (Fx*Gy - Fy*Gx) * (4/3) * (Fx^2 + Fy^2)^(-1/3) * (Fx*Gx + Fy*Gy) - - + + // (Fx*Hy - Fy*Hx) * (Fx^2 + Fy^2)^(2/3) + // (4/3) * (Fx*Gy - Fy*Gx) * (Fx*Gx + Fy*Gy) * (Fx^2 + Fy^2)^(-1/3) - - + + // (Fx*Hy - Fy*Hx) * (Fx^2 + Fy^2)^(2/3) + (Fx*Gy - Fy*Gx) * (2/3) * (Fx^2 + Fy^2)^(-1/3) * 2*(Fx*Gx + Fy*Gy) - + // A := lim dp->0 [(c(p + dp) - c(p)) / |dp|] - + // p(t) = f(t) // dp(t) = f'(t) * dt - + // dc/sqrt(dpx^2 + dpy^2) - - + + // A^2 = dc^2/(dpx^2 + dpy^2) - - + + // A^2 = dc^2/(f'x(t)^2*dt^2 + f'y(t)^2*dt^2) // A^2 = (1 / (f'x(t)^2 + f'y(t)^2)) * (dc^/dt)^2 // A = (1 / |f'|) * (dc/dt) - - - + + + // (c(f(t) + f'(t) * dt) - c(f(t))) / |f'(t) * dt| // (1 / |f'(t)|) * lim dt->0 [(C(t + dt) - C(t)) / dt] - + // (1 / |f'(t)|) * dC/dt - - + + // (1 / |f'(t)|) * (c(t + dt) - c(t)) / dt - - + + // c = (F.X*G.Y - F.Y*G.X) * (F.X^2 + F.Y^2)^(-3/2) - + // dc / dt = (G.X*G.Y + F.X*H.Y - G.Y*G.X - F.Y*H.X) * (F.X^2 + F.Y^2)^(-3/2) - // 3 * (F.X*G.Y - F.Y*G.X) * (F.X^2 + F.Y^2)^(-5/2) * (F.X*G.X + F.Y*G.Y) - - + + // dc / dt = (F.X*H.Y - F.Y*H.X) * (F.X^2 + F.Y^2)^(-3/2) - // 3 * (F.X*G.Y - F.Y*G.X) * (F.X^2 + F.Y^2)^(-5/2) * (F.X*G.X + F.Y*G.Y) - - + + // dc / dt = (F.X^2 + F.Y^2)^(-3/2) * [(F.X*H.Y - F.Y*H.X) - // 3 * (F.X*G.Y - F.Y*G.X) * (F.X*G.X + F.Y*G.Y) * (F.X^2 + F.Y^2)^-1] - - - + + + let F = derivative t seg let G = secondDerivative t seg let H = thirdDerivative t seg - + let lfSq = F.LengthSquared - + let diff = lfSq**(-1.5) * ((F.X*H.Y - F.Y*H.X) - 3.0 * (F.X*G.Y - F.Y*G.X) * (F.X*G.X + F.Y*G.Y) / lfSq) - // // - // let diff = + // + // let diff = // (F.X*H.Y - F.Y*H.X) * sqr lfSqCbrt + // (4.0/3.0) * (F.X*G.Y - F.Y*G.X) * (F.X*G.X + F.Y*G.Y) / lfSqCbrt diff / F.Length - + /// finds all parameters t <- [0;1] where the curvature changes its sign. /// note that only Bezier3 segments may have inflection points. let inflectionParameters (seg : PathSegment) = // {t | t >= 0 && t <= 1 && f'(t).X*f''(t).Y - f'(t).Y*f''(t).X == 0 } match seg with - | LineSeg _ - | ArcSeg _ + | LineSeg _ + | ArcSeg _ | Bezier2Seg _ -> [] @@ -1444,7 +1443,7 @@ module PathSegment = [t0] elif v1 then [t1] else [] - + /// gets a normalized tangent at the given parameter (t <- [0;1]) let tangent (t : float) (seg : PathSegment) = match seg with @@ -1456,11 +1455,11 @@ module PathSegment = let alpha = a0 + t * da let n = (cos alpha * e.Axis1 - sin alpha * e.Axis0) |> Vec.normalize if da > 0.0 then n else -n - + | Bezier2Seg(p0, p1, p2) -> - if t <= 0.0 then + if t <= 0.0 then Vec.normalize (p1 - p0) - elif t >= 1.0 then + elif t >= 1.0 then Vec.normalize (p2 - p1) else let u = 1.0 - t @@ -1479,17 +1478,17 @@ module PathSegment = let v = p2 - p1 let w = p3 - p2 Vec.normalize (s*s*u + 2.0*t*s*v + t*t*w) - + /// gets a normalized (left) normal at the given parameter (t <- [0;1]) let normal (t : float) (seg : PathSegment) = let t = tangent t seg V2d(-t.Y, t.X) /// gets an axis aligned bounding box for the given segment - let bounds (seg : PathSegment) = + let bounds (seg : PathSegment) = PathSegmentIntersections.bounds seg - /// reverses the given segment s.t. reversed(t) = original(1-t) + /// reverses the given segment s.t. reversed(t) = original(1-t) let reverse (seg : PathSegment) = match seg with | LineSeg(p0, p1) -> LineSeg(p1, p0) @@ -1503,14 +1502,14 @@ module PathSegment = | LineSeg(p0, p1) -> line (f p0) (f p1) | Bezier2Seg(p0, p1, p2) -> bezier2 (f p0) (f p1) (f p2) | Bezier3Seg(p0, p1, p2, p3) -> bezier3 (f p0) (f p1) (f p2) (f p3) - | ArcSeg(p0, p1, a0, da, ellipse) -> + | ArcSeg(p0, p1, a0, da, ellipse) -> let c = f ellipse.Center let ax0 = f (ellipse.Center + ellipse.Axis0) - c let ax1 = f (ellipse.Center + ellipse.Axis1) - c let e = Ellipse2d.FromConjugateDiameters(c, ax0, ax1) let q0 = f p0 let q1 = f p1 - let da = + let da = if not e.IsCcw then -da else da let a0 = e.GetAlpha q0 @@ -1524,7 +1523,7 @@ module PathSegment = Some s else match s with - | LineSeg(p0,_) -> + | LineSeg(p0,_) -> tryLine p0 (point t1 s) | ArcSeg(p0, _, a0, da, e) -> @@ -1555,7 +1554,7 @@ module PathSegment = Some s else match s with - | LineSeg(_,p1) -> + | LineSeg(_,p1) -> tryLine (point t0 s) p1 | ArcSeg(p0, p1, a0, da, e) -> @@ -1588,7 +1587,7 @@ module PathSegment = withT0 t0 s elif t0 < t1 then match s with - | LineSeg(_,_) -> + | LineSeg(_,_) -> let p0 = point t0 s let p1 = point t1 s tryLine p0 p1 @@ -1607,7 +1606,7 @@ module PathSegment = let r0 = Ray2d(p0, d0) let r1 = Ray2d(p1, -d1) - + let mutable t = 0.0 if r0.Intersects(r1, &t) then let c = r0.GetPointOnRay t @@ -1616,7 +1615,7 @@ module PathSegment = tryLine p0 p1 | Bezier3Seg _ -> - + match withT0 t0 s with | Some r -> withT1 ((t1 - t0) / (1.0 - t0)) r @@ -1624,8 +1623,8 @@ module PathSegment = None else None - - + + /// tries to split the segment into two new segments having ranges [0;t] and [t;1] respectively. let splitWithCenterPoint (t : float) (pt : V2d) (s : PathSegment) = if t <= 0.0 then (None, Some s) @@ -1665,10 +1664,10 @@ module PathSegment = let split (t : float) (s : PathSegment) = let pt = point t s splitWithCenterPoint t pt s - + /// tries to get the t-parameter for a point very near the segment (within eps) let tryGetT (eps : float) (pt : V2d) (segment : PathSegment) = - + let newtonImprove (iter : int) (t : float) = let mutable t = t let mutable dt = 1000.0 @@ -1676,29 +1675,29 @@ module PathSegment = while iter > 0 && not (Fun.IsTiny (dt, 1E-15)) do // err^2 = min // 2*err * err' = 0 - + // f = 2*err * err' // f' = 2*(err'^2 + err*err'') - + let err = point t segment - pt let d = derivative t segment let dd = secondDerivative t segment - + //let v = Vec.lengthSquared err let dv = 2.0 * Vec.dot err d let ddv = 2.0 * (Vec.dot d d + Vec.dot err dd) - - + + dt <- -0.8 * dv / ddv //printfn "%d: %A %A -> %A" iter t dv dt t <- t + dt iter <- iter - 1 - + let t = clamp 0.0 1.0 t let err = point t segment - pt if Fun.IsTiny(err, eps) then Some t else None - + match segment with | LineSeg(p0, p1) -> if Fun.ApproximateEquals(p0, pt, eps) then Some 0.0 @@ -1719,19 +1718,19 @@ module PathSegment = elif Fun.ApproximateEquals(p2, pt, eps) then Some 1.0 else let box = Box2d([p0; p1; p2]).EnlargedBy eps - + if box.Contains pt then let a = p2 + p0 - 2.0*p1 let b = 2.0*(p1 - p0) let c = p0 - pt - - let struct(t0, t1) = + + let struct(t0, t1) = if box.Size.MajorDim = 0 then Polynomial.RealRootsOf(a.X, b.X, c.X) else Polynomial.RealRootsOf(a.Y, b.Y, c.Y) - + let d0 = if t0 >= 0.0 && t0 <= 1.0 then Vec.length (a * sqr t0 + b * t0 + c) else System.Double.PositiveInfinity let d1 = if t1 >= 0.0 && t1 <= 1.0 then Vec.length (a * sqr t1 + b * t1 + c) else System.Double.PositiveInfinity - + if d0 < d1 then newtonImprove 20 t0 else @@ -1739,45 +1738,45 @@ module PathSegment = else None else None - + //t^2 * (p2 + p0 - 2*p1) + t * 2*(p1 - p0) + p0 - + | Bezier3Seg(p0, p1, p2, p3) -> if Fun.ApproximateEquals(p0, pt, eps) then Some 0.0 elif Fun.ApproximateEquals(p3, pt, eps) then Some 1.0 else let box = Box2d([p0; p1; p2; p3]).EnlargedBy eps - + if box.Contains pt then let a = -p0 + 3.0*p1 - 3.0*p2 + p3 let b = 3.0*p0 - 6.0*p1 + 3.0*p2 let c = 3.0*p1 - 3.0*p0 let d = p0 - pt - - let struct(t0, t1, t2) = + + let struct(t0, t1, t2) = if box.Size.MajorDim = 0 then Polynomial.RealRootsOf(a.X, b.X, c.X, d.X) else Polynomial.RealRootsOf(a.Y, b.Y, c.Y, d.Y) - - + + let t0 = if t0 >= -eps && t0 <= 1.0 + eps then newtonImprove 10 t0 else None let t1 = if t1 >= -eps && t1 <= 1.0 + eps then newtonImprove 10 t1 else None let t2 = if t2 >= -eps && t2 <= 1.0 + eps then newtonImprove 10 t2 else None - + let d0 = match t0 with | Some t0 -> Vec.length (a * sqr t0 + b * t0 + c) | _ -> System.Double.PositiveInfinity let d1 = match t1 with | Some t1 -> Vec.length (a * sqr t1 + b * t1 + c) | _ -> System.Double.PositiveInfinity let d2 = match t2 with | Some t2 -> Vec.length (a * sqr t2 + b * t2 + c) | _ -> System.Double.PositiveInfinity - - - let result = + + + let result = if d0 < d1 then if d0 < d2 then t0 else t2 else if d1 < d2 then t1 else t2 - + result |> Option.bind (newtonImprove 30) - + else None | ArcSeg(p0, p1, a, da, e) -> @@ -1785,17 +1784,17 @@ module PathSegment = elif Fun.ApproximateEquals(p1, pt, eps) then Some 1.0 else let m = M33d.FromCols(V3d(e.Axis0, 0.0), V3d(e.Axis1, 0.0), V3d(e.Center, 1.0)) - + let c = m.Inverse.TransformPos(pt) |> Vec.normalize let angle = atan2 c.Y c.X - + let t = arcT a da angle - + if Fun.ApproximateEquals(m.TransformPos c, pt, eps) then newtonImprove 30 t else - None - + None + /// tries to merge two segments together resulting in an optional new segment (or None if the segments cannot be merged) let tryMerge (eps : float) (a : PathSegment) (b : PathSegment) = match a with @@ -1822,7 +1821,7 @@ module PathSegment = else let ta1 = a2 - a1 |> Vec.normalize let tb0 = b1 - b0 |> Vec.normalize - + if Fun.ApproximateEquals(ta1, tb0, 1E-9) then let ra = Ray2d(a0, a1 - a0) let rb = Ray2d(b2, b2 - b1) @@ -1862,13 +1861,13 @@ module PathSegment = | _ -> // TODO: proper merge for Arc/Bezier3 None - - + + let private overlapTs = [| 0.25; 0.5; 0.75; 0.333 |] - + /// tries to find the overlapping t-ranges for two segments. The first range will be sorted whereas the second one may not be. let overlap (eps : float) (a : PathSegment) (b : PathSegment) = - + let isOnLine (p0 : V2d) (p1 : V2d) (p : V2d) = let u = p1 - p0 let len = Vec.length u @@ -1884,10 +1883,10 @@ module PathSegment = None else None - - + + let inline checkRange ((aRange : Range1d, bRange : V2d) as tup) = - let isOverlapping = + let isOverlapping = overlapTs |> Array.forall (fun t -> let ta = lerp aRange.Min aRange.Max t let tb = lerp bRange.X bRange.Y t @@ -1897,11 +1896,11 @@ module PathSegment = Some tup else None - - + + let a0 = startPoint a let a1 = endPoint a - + let b0 = startPoint b let b1 = endPoint b @@ -1924,7 +1923,7 @@ module PathSegment = checkRange (Range1d(tab0, 1.0), V2d(0.0, tba1)) | None -> None - | None -> + | None -> match tryGetT eps b1 a with | Some tab1 -> match tryGetT eps a0 b with @@ -1946,14 +1945,14 @@ module PathSegment = None | None -> None - + /// splits a PathSegment given two ordered ts into possibly three parts (the range must not be empty or invalid) let splitRange (r : Range1d) (seg : PathSegment) : option * PathSegment * option = let l, rest = split r.Min seg let m, r = split ((r.Max - r.Min) / (1.0 - r.Min)) (Option.get rest) - l, Option.get m, r - - + l, Option.get m, r + + /// splits the segment at the given parameter values and returns the list of resulting segments. let splitMany (ts : list) (s : PathSegment) = match ts with @@ -1968,7 +1967,7 @@ module PathSegment = else let ts = ts |> List.map (fun t -> (t-t0)/(1.0-t0)) let l, r = split t0 s - let r = + let r = match r with | Some r -> run ts r | None -> [] @@ -1998,11 +1997,11 @@ module ``PathSegment Public API`` = match t1 with | Some t1 -> PathSegment.withT1 t1 x | None -> Some x - + member x.Item with inline get(t : float) = PathSegment.point t x - + let (|Line|Bezier2|Bezier3|Arc|) (s : PathSegment) = @@ -2023,7 +2022,7 @@ module ``PathSegment Public API`` = static member ForceBezier3(p0, p1, p2, p3) = Bezier3Seg(p0, p1, p2, p3) static member ForceArc(p0, p1, a0, da, e) = ArcSeg(p0, p1, a0, da, e) - + module PathSegment = let (|Line|Bezier2|Bezier3|Arc|) (s : PathSegment) = match s with diff --git a/src/Aardvark.Base.Fonts/PathTessellator.fs b/src/Aardvark.Base.Fonts/PathTessellator.fs index b3e01fe2..d8702305 100644 --- a/src/Aardvark.Base.Fonts/PathTessellator.fs +++ b/src/Aardvark.Base.Fonts/PathTessellator.fs @@ -1,17 +1,19 @@ -namespace Aardvark.Rendering.Text +namespace Aardvark.Base.Fonts open FSharp.Data.Adaptive open Aardvark.Base -open Aardvark.Rendering -open Aardvark.Rendering.Text -open Aardvark.Rendering.Text.BvhImpl +open BvhImpl + +type ShapeGeometry = + { Positions : V3f[] + Coordinates : V4f[] } module internal Tessellator = open LibTessDotNet.Double + [] - module private Helpers = + module private Helpers = - type Ellipse2d with member internal x.GetControlPoint(alpha0 : float, alpha1 : float) = let p0 = x.GetPoint alpha0 @@ -35,7 +37,7 @@ module internal Tessellator = let c0 = System.Collections.Generic.List() for p in l.Points do c0.Add(ContourVertex(Vec3(X = p.X, Y = p.Y))) c0.Add(c0.[0]) - + let c1 = System.Collections.Generic.List() for p in r.Points do c1.Add(ContourVertex(Vec3(X = p.X, Y = p.Y))) c1.Add(c1.[0]) @@ -74,7 +76,7 @@ module internal Tessellator = true else false - + let inline private sgn (eps : float) (v : float) = if v < -eps then -1 elif v > eps then 1 @@ -95,7 +97,7 @@ module internal Tessellator = if side0 = 0 then side0 <- v0 elif side0 <> v0 then side0 <- 10 - + if side1 = 0 then side1 <- v1 elif side1 <> v1 then side1 <- 10 @@ -104,19 +106,19 @@ module internal Tessellator = intersects || (side0 = 1 || side0 = -1) || (side1 = 1 || side1 = -1) - + let overlaps (l : Part) (r : Part) = match l, r with | LinePart l, LinePart r -> false //linesIntersect 1E-7 l r | PolygonalPart(_,l), LinePart r -> polyAndLineIntersect 1E-7 l r | LinePart l, PolygonalPart(_,r) -> polyAndLineIntersect 1E-7 r l - | PolygonalPart(_,l), PolygonalPart(_,r) -> + | PolygonalPart(_,l), PolygonalPart(_,r) -> intersectsConvex 1E-6 l r let ofPathSegment (s : PathSegment) = match s with - | Line(p0, p1) -> + | Line(p0, p1) -> LinePart(Line2d(p0, p1)) | Bezier2(p0, p1, p2) -> PolygonalPart(s, Polygon2d [| p0; p1; p2 |]) @@ -130,7 +132,7 @@ module internal Tessellator = match p with | LinePart l -> l.BoundingBox2d | PolygonalPart(_,p) -> p.BoundingBox2d - + let toPathSegment (s : Part) = match s with | LinePart l -> PathSegment.line l.P0 l.P1 @@ -138,7 +140,7 @@ module internal Tessellator = let toLineArray (p : Part) = match p with - | LinePart(l) -> + | LinePart(l) -> [| l.P0; l.P1 |] | PolygonalPart(_,p) -> let arr = Array.zeroCreate (2 * p.PointCount) @@ -160,9 +162,9 @@ module internal Tessellator = | Bezier2(p0, p1, _) -> p0 | Bezier3(p0, p1, _, _) -> p0 | Arc(p0, _, _, _, _) -> p0 - | LinePart l -> + | LinePart l -> l.P0 - + let endPoint (p : Part) = match p with | PolygonalPart(s,_) -> @@ -171,27 +173,27 @@ module internal Tessellator = | Bezier2(_, _, p2) -> p2 | Bezier3(_, _, _, p3) -> p3 | Arc(_, p1, _, _, _) -> p1 - | LinePart l -> + | LinePart l -> l.P1 let startTangent (p : Part) = match p with | PolygonalPart(s,_) -> PathSegment.tangent 0.0 s - - | LinePart l -> + + | LinePart l -> Vec.normalize (l.P1 - l.P0) - + let endTangent (p : Part) = match p with | PolygonalPart(s,_) -> PathSegment.tangent 1.0 s - | LinePart l -> + | LinePart l -> Vec.normalize (l.P1 - l.P0) let toBvh (positions : V2d[], triangleIndex : int[]) = let mutable bvh = BvhTree2d.empty - + let mutable bi = 0 let maxe = triangleIndex.Length - 2 let mutable ti = 0 @@ -209,7 +211,7 @@ module internal Tessellator = let toBvhLine (polys : V2d[][]) = let mutable bvh = BvhTree2d.empty - + for poly in polys do if poly.Length > 1 then let mutable p0 = poly.[poly.Length - 1] @@ -243,7 +245,7 @@ module internal Tessellator = //let ccw = u.X*v.Y - u.Y*v.X > 0.0 - //let intersects = + //let intersects = // if ccw then t.Contains pt // else Triangle2d(t.P0, t.P2, t.P1).Contains pt @@ -271,10 +273,10 @@ module internal Tessellator = bvh.GetIntersecting(b, fun _ _ t -> if Fun.ApproximateEquals(t.P0, pt, 1E-9) || Fun.ApproximateEquals(t.P1, pt, 1E-9) || Fun.ApproximateEquals(t.P2, pt, 1E-9) then Some t else None) |> HashMap.isEmpty |> not - + let containsPointLine (bvh : BvhTree2d) (pt : V2d) = let b = Box2d.FromCenterAndSize(pt, V2d.II * 1E-5) - bvh.GetIntersecting(b, fun _ _ t -> + bvh.GetIntersecting(b, fun _ _ t -> let r = pt.GetClosestPointOn t if Fun.ApproximateEquals(r, pt, 1E-9) then Some t else None @@ -288,20 +290,20 @@ module internal Tessellator = if Fun.ApproximateEquals(p0, p1, 1E-9) then // has point - bvh.GetIntersecting(b, fun _ _ t -> - if (Fun.ApproximateEquals(t.P0, edge.P0, 1E-9) || Fun.ApproximateEquals(t.P1, edge.P1, 1E-9)) then - Some t - else + bvh.GetIntersecting(b, fun _ _ t -> + if (Fun.ApproximateEquals(t.P0, edge.P0, 1E-9) || Fun.ApproximateEquals(t.P1, edge.P1, 1E-9)) then + Some t + else None ) |> HashMap.isEmpty |> not else - bvh.GetIntersecting(b, fun _ _ t -> - if (Fun.ApproximateEquals(t.P0, edge.P0, 1E-9) && Fun.ApproximateEquals(t.P1, edge.P1, 1E-9)) || - (Fun.ApproximateEquals(t.P1, edge.P0, 1E-9) && Fun.ApproximateEquals(t.P0, edge.P1, 1E-9)) then - Some t - else + bvh.GetIntersecting(b, fun _ _ t -> + if (Fun.ApproximateEquals(t.P0, edge.P0, 1E-9) && Fun.ApproximateEquals(t.P1, edge.P1, 1E-9)) || + (Fun.ApproximateEquals(t.P1, edge.P0, 1E-9) && Fun.ApproximateEquals(t.P0, edge.P1, 1E-9)) then + Some t + else None ) |> HashMap.isEmpty @@ -311,7 +313,7 @@ module internal Tessellator = let a = t0.X * t1.Y - t0.Y * t1.X let b = t0.X * t1.X + t0.Y * t1.Y atan2 a b - + let boundary (rule : WindingRule) (parts : seq<#seq>) = let tess = Tess() let emptys = System.Collections.Generic.List() @@ -330,7 +332,7 @@ module internal Tessellator = emptys.Add arr tess.Tessellate(rule, ElementType.BoundaryContours, normal = Vec3(0.0, 0.0, 1.0)) - + let mutable i = 0 let res = Array.zeroCreate (emptys.Count + tess.ElementCount) let mutable pi = 0 @@ -344,7 +346,7 @@ module internal Tessellator = let cnt = tess.Elements.[i+1] if cnt > 2 then let arr = Array.zeroCreate cnt - + let mutable oi = 0 for vi in i0 .. i0 + cnt - 1 do let v = tess.Vertices.[vi] @@ -378,7 +380,7 @@ module internal Tessellator = else [||], [||] - + module private Coords = let bezier3 (q0 : V2d) (q1 : V2d) (q2 : V2d) (q3 : V2d) = let m3 = @@ -388,14 +390,14 @@ module internal Tessellator = +3.0, -6.0, 3.0, 0.0, -1.0, 3.0, -3.0, 1.0 ) - + let m3i = M44d( 1.0, 0.0, 0.0, 0.0, 1.0, 1.0/3.0, 0.0, 0.0, 1.0, 2.0/3.0, 1.0/3.0, 0.0, 1.0, 1.0, 1.0, 1.0 - ) + ) let b = M44d.FromRows( @@ -420,12 +422,12 @@ module internal Tessellator = let delta2 = d1*d2 - d0*d3 let delta3 = d1*d3 - d2*d2 let discr = 4.0*delta1*delta3 - delta2*delta2 - + let inline flip (v : V3d) = V3d(-v.X, -v.Y, v.Z) let qs2 = 3.0*d2*d2 - 4.0*d1*d3 - if qs2 >= 0.0 && not (Fun.IsTiny(d1, 1E-8)) then + if qs2 >= 0.0 && not (Fun.IsTiny(d1, 1E-8)) then // serpentine / Cusp with inflection at infinity let qs = sqrt(qs2 / 3.0) let l = V2d(d2 + qs, 2.0*d1).Normalized @@ -449,7 +451,7 @@ module internal Tessellator = if shouldFlip then Choice1Of2(7, flip w.R0.XYZ, flip w.R1.XYZ, flip w.R2.XYZ, flip w.R3.XYZ) else Choice1Of2(7, w.R0.XYZ, w.R1.XYZ, w.R2.XYZ, w.R3.XYZ) - elif qs2 < 0.0 && not (Fun.IsTiny(d1, 1E-8)) then + elif qs2 < 0.0 && not (Fun.IsTiny(d1, 1E-8)) then // loop let ql = sqrt(-qs2) let d = V2d(d2 + ql, 2.0*d1).Normalized @@ -466,9 +468,9 @@ module internal Tessellator = let va = a >= 1E-3 && a <= 1.0 - 1E-3 let vb = b >= 1E-3 && b <= 1.0 - 1E-3 - if va then + if va then Choice2Of2(PathSegment.splitMany [a] (PathSegment.bezier3 q0 q1 q2 q3)) - elif vb then + elif vb then Choice2Of2(PathSegment.splitMany [b] (PathSegment.bezier3 q0 q1 q2 q3)) else let F = @@ -500,11 +502,11 @@ module internal Tessellator = -sl, -3.0*sl*tl*tl, 0.0, 0.0, 0.0, 3.0*sl*sl*tl, 0.0, 0.0, 0.0, -sl*sl*sl, 0.0, 0.0 - ) - + ) + let w = m3i * F Choice1Of2(6, w.R0.XYZ, w.R1.XYZ, w.R2.XYZ, w.R3.XYZ) - + elif Fun.IsTiny(d1, 1E-8) && Fun.IsTiny(d2, 1E-8) && not (Fun.IsTiny(d3, 1E-8)) then let qc = 1.5*q2 - 0.5*q3 //(3.0*(q1 + q2) - q0 - q3)/4.0 match PathSegment.tryBezier2 q0 qc q3 with @@ -581,7 +583,7 @@ module internal Tessellator = let ts = ts |> List.map (fun (t,p) -> (t-t0)/(1.0-t0), p) let l, r = split t0 p0 s - let r = + let r = match r with | Some r -> run ts r | None -> [] @@ -593,21 +595,21 @@ module internal Tessellator = let splitManyMany (l : PathSegment) (intersections : seq<'a * PathSegment * list>) : list * HashMap<'a, list> = let intersections = Seq.toList intersections - let all = - intersections + let all = + intersections |> List.collect (fun (id, r, ts) -> ts |> List.map (fun (tl, tr) -> tl, (id, r, tr))) |> List.sortBy fst let rec run (splits : HashMap<'a, PathSegment * Map>) (interactions : list) (l : PathSegment) = match interactions with - | [] -> + | [] -> [l], splits | (tl, (ri, r, tr)) :: rest -> let l0, l1 = PathSegment.split tl l - match l1 with + match l1 with | Some l1 -> - + let pt = PathSegment.startPoint l1 let inline update (old : option>) = match old with @@ -615,7 +617,7 @@ module internal Tessellator = Some (s, Map.add tr pt m) | None -> Some(r, Map.ofList [tr, pt]) - + let splits = HashMap.alter ri update splits let rest = rest |> List.map (fun (ti, r) -> (ti - tl) / (1.0 - tl), r) @@ -630,7 +632,7 @@ module internal Tessellator = let self, result = run HashMap.empty all l - let others = + let others = result |> HashMap.map (fun _ (s, m) -> splitMany (Map.toList m) s ) @@ -638,15 +640,15 @@ module internal Tessellator = self, others - let toGeometry (rule : WindingRule) (bounds : Box2d) (inputPath : seq) = - let trafo = + let toGeometry (rule : WindingRule) (bounds : Box2d) (inputPath : seq) : ShapeGeometry = + let trafo = let size = bounds.Size.Length - let scale = + let scale = if Fun.IsTiny size then 1.0 else 1.0 / size Trafo2d.Translation(-bounds.Center) * Trafo2d.Scale scale - + let path = let trafo (pt : V2d) = trafo.Forward.TransformPos pt inputPath |> Seq.map (PathSegment.transform trafo) |> Seq.toList @@ -656,9 +658,9 @@ module internal Tessellator = | PolygonalPart(s, _) -> let (l,r) = PathSegment.split 0.5 s match l, r with - | Some l, Some r -> + | Some l, Some r -> [l;r] - | Some v, None + | Some v, None | None, Some v -> [v] | None, None -> @@ -670,7 +672,7 @@ module internal Tessellator = let a = t0.X * t1.Y - t0.Y * t1.X let b = t0.X * t1.X + t0.Y * t1.Y atan2 a b - + let neededSplits (s : PathSegment) = match s with | Bezier3(p0, p1, p2, p3) -> @@ -712,7 +714,7 @@ module internal Tessellator = use e = repl.GetEnumerator() if e.MoveNext() then let _,_,r = IndexList.neighbours i l - let indexAfter = + let indexAfter = match r with | Some(ri,_) -> fun i -> Index.between i ri | None -> Index.after @@ -727,7 +729,7 @@ module internal Tessellator = b <- BvhTree2d.add i (PathSegment.bounds e.Current) e.Current b l, b - else + else let l1 = IndexList.remove i l let b1 = BvhTree2d.remove i bvh l1, b1 @@ -735,12 +737,12 @@ module internal Tessellator = match stack with | s :: stack -> let b = PathSegment.bounds s - - let intersecting = + + let intersecting = let teps = 1E-7 - bvh.GetIntersecting(b, fun _ _ other -> + bvh.GetIntersecting(b, fun _ _ other -> let all = PathSegment.intersections 1E-9 s other - let parameters = + let parameters = all |> List.filter (fun (ta, tb) -> (ta >= teps && ta <= 1.0-teps) || (tb >= teps && tb <= 1.0-teps)) @@ -755,24 +757,24 @@ module internal Tessellator = let bvh1 = bvh.Add(idx, b, s) nonIntersecting result1 bvh1 stack else - + let self, others = splitManyMany s (HashMap.toSeq intersecting |> Seq.map (fun (a,(b,c)) -> a,b,c)) - //let mine = - // intersecting + //let mine = + // intersecting // |> HashMap.toList // |> List.collect (fun (o, (_, ts)) -> ts |> List.map fst) // |> List.sort //let self = s |> PathSegment.splitMany mine - + let mutable r = result let mutable b = bvh for (oi, res) in others do let (r1, b1) = replace oi res r b r <- r1 b <- b1 - + //let mutable r = result //let mutable b = bvh @@ -796,16 +798,16 @@ module internal Tessellator = match stack with | s :: stack -> let replacements = neededSplits s - + match replacements with | Some repl -> nonOverlapping result bvh (repl @ stack) - | None -> + | None -> let part = Part.ofPathSegment s let b = Part.bounds part - - let intersecting = - bvh.GetIntersecting(b, fun _ _ other -> + + let intersecting = + bvh.GetIntersecting(b, fun _ _ other -> if Part.overlaps part other then Some other else None ) @@ -815,14 +817,14 @@ module internal Tessellator = let result1 = IndexList.set idx part result let bvh1 = bvh.Add(idx, b, part) nonOverlapping result1 bvh1 stack - else + else let (bvh, result) = - intersecting + intersecting |> HashMap.choose (fun _ o -> match o with | PolygonalPart _ -> Some o | _ -> None) |> HashMap.fold (fun (obvh : BvhTree2d, result : IndexList) idx part -> let bvh = BvhTree2d.remove idx obvh match split part with - | [] -> + | [] -> (bvh, IndexList.remove idx result) | [n] -> let np = Part.ofPathSegment n @@ -865,12 +867,12 @@ module internal Tessellator = let t0 = Part.startTangent part let t1 = Part.endTangent part let selfAngle = turningAngle t0 t1 - let cornerAngle = + let cornerAngle = match IndexList.tryLast current with - | Some last -> + | Some last -> let tl = Part.endTangent last turningAngle tl t0 - | _ -> + | _ -> 0.0 let idx = Index.after current.MaxIndex let result1 = IndexList.set idx part current @@ -880,25 +882,25 @@ module internal Tessellator = | Some start when Fun.ApproximateEquals(Part.startPoint start, p1, 1E-8) -> let cornerAngle = turningAngle (Part.endTangent part) (Part.startTangent start) let exteriorAngle2 = exteriorAngle1 + cornerAngle - + let rest = findClosed 0.0 V2d.NaN IndexList.empty stack (exteriorAngle2, result1) :: rest - | _ -> + | _ -> findClosed exteriorAngle1 p1 result1 stack else match Seq.tryHead current with | Some start -> findClosed exteriorAngle last current (LinePart(Line2d(last, Part.startPoint start)) :: part :: stack) - | _ -> + | _ -> findClosed 0.0 V2d.NaN IndexList.empty stack | [] -> match Seq.tryHead current with | Some start -> findClosed exteriorAngle last current [LinePart(Line2d(last, Part.startPoint start))] - | _ -> + | _ -> [] - + let parts = findClosed 0.0 V2d.NaN IndexList.empty cleaned |> IndexList.ofList // triangulate the non-curved boundary @@ -920,13 +922,13 @@ module internal Tessellator = let nonCurved = toBvh (pos, idx) // find the final solid boundary and triangulate it - let boundary = + let boundary = let tess = Tess() let emptys = System.Collections.Generic.List() for (_exterior, path) in parts do if not (IndexList.isEmpty path) then let contour = System.Collections.Generic.List() - + let inline add (v : V2d) = contour.Add(ContourVertex(Vec3(X = v.X, Y = v.Y))) @@ -943,10 +945,10 @@ module internal Tessellator = if containsPoint nonCurved p1 then add p1 | Bezier3(p0, p1, p2, p3) -> - + let points = [|p0;p1;p2;p3|] - let hull = Polygon2d(points).ComputeConvexHullIndexPolygon().Indices |> Seq.toArray - + let hull = Polygon2d(points).ComputeConvexHullIndexPolygon().Indices |> Seq.toArray + add p0 if Array.contains 1 hull && containsPoint nonCurved p1 then add p1 @@ -975,7 +977,7 @@ module internal Tessellator = res.[i] <- e i <- i + 1 - for _ in 0 .. tess.ElementCount - 1 do + for _ in 0 .. tess.ElementCount - 1 do let o = tess.Elements.[ei] let c = tess.Elements.[ei+1] let arr = Array.zeroCreate c @@ -991,7 +993,7 @@ module internal Tessellator = i <- i + 1 res - let pos, idx = + let pos, idx = boundary |> triangulate WindingRule.Positive @@ -1037,7 +1039,7 @@ module internal Tessellator = // since all polygonal parts are non-overlapping, convex and are connected to at least two boundary points // it should be sufficient to test whether or not their "center" point is covered by the solid part. let solid = toBvh (pos, idx) - for (extAngle,path) in parts do + for (extAngle,path) in parts do for s in path do match s with | PolygonalPart(s, _) -> @@ -1045,7 +1047,7 @@ module internal Tessellator = let inside = containsPoint solid c if not inside then match s with - | Line _ -> + | Line _ -> () | Bezier2(p0, p1, p2) -> @@ -1057,11 +1059,11 @@ module internal Tessellator = add p0 p1 p2 (V4f(0,0,1,3)) (V4f(0.5, 0.0, 1.0,3.0)) (V4f(1,1,1,3)) - | Arc(p0, p2, alpha0, dAlpha, ellipse) -> + | Arc(p0, p2, alpha0, dAlpha, ellipse) -> let uv2World = M33d.FromCols(V3d(ellipse.Axis0, 0.0), V3d(ellipse.Axis1, 0.0), V3d(ellipse.Center, 1.0)) let world2UV = uv2World.Inverse let p1 = ellipse.GetControlPoint(alpha0, alpha0 + dAlpha) - + if hasEdge edges p0 p2 || (hasEdge edges p0 p1 && hasEdge edges p1 p2) then let c0 = world2UV.TransformPos p0 let c1 = world2UV.TransformPos p1 @@ -1073,28 +1075,28 @@ module internal Tessellator = else add p0 p1 p2 (V4f(c0.X, c0.Y,1.0, 5.0)) (V4f(c1.X, c1.Y,1.0, 5.0)) (V4f(c2.X, c2.Y,1.0,5.0)) - + | Bezier3(p0, p1, p2, p3) -> match Coords.bezier3 p0 p1 p2 p3 with | Choice1Of2(k, c0, c1, c2, c3) -> - - let touching = + + let touching = hasEdge edges p0 p3 || ( let e01 = hasEdge edges p0 p1 let e12 = hasEdge edges p1 p2 let e23 = hasEdge edges p2 p3 - (e01 && e12 && e23) || - (e01 && hasEdge edges p1 p3) || + (e01 && e12 && e23) || + (e01 && hasEdge edges p1 p3) || (e23 && hasEdge edges p0 p2) ) if touching then - + let points = [|p0;p1;p2;p3|] let weights = [|c0;c1;c2;c3|] - let hull = Polygon2d(points).ComputeConvexHullIndexPolygon().Indices |> Seq.toArray - + let hull = Polygon2d(points).ComputeConvexHullIndexPolygon().Indices |> Seq.toArray + let ws = hull |> Array.map (fun i -> weights.[i]) let ps = hull |> Array.map (fun i -> points.[i]) @@ -1111,14 +1113,14 @@ module internal Tessellator = if hull.Length = 3 then add ps.[0] ps.[1] ps.[2] (V4f(ws.[0], float k)) (V4f(ws.[1], float k)) (V4f(ws.[2],float k)) - else + else add ps.[0] ps.[1] ps.[3] (V4f(ws.[0], float k)) (V4f(ws.[1], float k)) (V4f(ws.[3],float k)) add ps.[3] ps.[1] ps.[2] (V4f(ws.[3], float k)) (V4f(ws.[1], float k)) (V4f(ws.[2],float k)) - - - | _ -> + + + | _ -> failwith "should have been subdivided" @@ -1129,15 +1131,4 @@ module internal Tessellator = System.Array.Resize(&positions, cnt) System.Array.Resize(&coords, cnt) - IndexedGeometry( - Mode = IndexedGeometryMode.TriangleList, - IndexedAttributes = - SymDict.ofList [ - DefaultSemantic.Positions, positions :> System.Array - Symbol.Create "KLMKind", coords :> System.Array - ] - ) - - - - + { Positions = positions; Coordinates = coords } \ No newline at end of file diff --git a/src/Aardvark.sln b/src/Aardvark.sln index 247b0506..f243cdb4 100644 --- a/src/Aardvark.sln +++ b/src/Aardvark.sln @@ -72,6 +72,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "._", "._", "{61D029CD-C474- EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Aardvark.Base.Fonts", "Aardvark.Base.Fonts\Aardvark.Base.Fonts.fsproj", "{C6C9C0AE-0900-4F21-86C6-2202E44A6013}" EndProject +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Aardvark.Base.Fonts.Tests", "Tests\Aardvark.Base.Fonts.Tests\Aardvark.Base.Fonts.Tests.fsproj", "{D45E8A40-22A3-44F5-90EA-D42D5D7A522B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -174,6 +176,10 @@ Global {C6C9C0AE-0900-4F21-86C6-2202E44A6013}.Debug|Any CPU.Build.0 = Debug|Any CPU {C6C9C0AE-0900-4F21-86C6-2202E44A6013}.Release|Any CPU.ActiveCfg = Release|Any CPU {C6C9C0AE-0900-4F21-86C6-2202E44A6013}.Release|Any CPU.Build.0 = Release|Any CPU + {D45E8A40-22A3-44F5-90EA-D42D5D7A522B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D45E8A40-22A3-44F5-90EA-D42D5D7A522B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D45E8A40-22A3-44F5-90EA-D42D5D7A522B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D45E8A40-22A3-44F5-90EA-D42D5D7A522B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -202,6 +208,7 @@ Global {FBB17215-CDCE-4FFA-B10A-EA1B320C8638} = {C557CE9D-81EE-4EB3-AB94-B9F9CB2FA19A} {F278A2F7-52FF-40CD-B16D-4D82F7DFA8C3} = {A79411F9-60B3-46C8-8981-6905D9B9F74C} {C6C9C0AE-0900-4F21-86C6-2202E44A6013} = {C557CE9D-81EE-4EB3-AB94-B9F9CB2FA19A} + {D45E8A40-22A3-44F5-90EA-D42D5D7A522B} = {A79411F9-60B3-46C8-8981-6905D9B9F74C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1537AED0-2BB3-4EC3-B2CB-06E22C23B002} diff --git a/src/Tests/Aardvark.Base.Fonts.Tests/Aardvark.Base.Fonts.Tests.fsproj b/src/Tests/Aardvark.Base.Fonts.Tests/Aardvark.Base.Fonts.Tests.fsproj new file mode 100644 index 00000000..68a2cfd8 --- /dev/null +++ b/src/Tests/Aardvark.Base.Fonts.Tests/Aardvark.Base.Fonts.Tests.fsproj @@ -0,0 +1,27 @@ + + + + net8.0 + false + Exe + true + $(MSBuildThisFileDirectory)\test.runsettings + 3389;3390;3395 + + + ..\..\..\bin\Debug\ + DEBUG;TRACE + + + ..\..\..\bin\Release\ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Tests/Aardvark.Base.Fonts.Tests/Common.fs b/src/Tests/Aardvark.Base.Fonts.Tests/Common.fs new file mode 100644 index 00000000..f5cd212d --- /dev/null +++ b/src/Tests/Aardvark.Base.Fonts.Tests/Common.fs @@ -0,0 +1,100 @@ +namespace Expecto + + +open Expecto +open Aardvark.Base +open Aardvark.Base.Fonts +open FsCheck +open FsCheck.TypeClass + +type ZeroOne = ZeroOne of float + +type Generators() = + static member V2d = + { new Arbitrary() with + override x.Generator = + gen { + let! (NormalFloat a) = Arb.generate + let! (NormalFloat b)= Arb.generate + return V2d(a,b) + } + } + + static member ZeroOne = + { new Arbitrary() with + override x.Generator = + gen { + let! (NormalFloat a) = Arb.generate + return ZeroOne (abs a % 1.0) + } + } + + static member PathSegment = + { new Arbitrary() with + override x.Generator = + gen { + let! kind = Gen.elements [0;1;2;3] + + match kind with + | 0 -> + let! p0 = Arb.generate + let! p1 = Arb.generate |> Gen.filter (fun p -> not (Fun.ApproximateEquals(p, p0, 1E-8))) + return PathSegment.line p0 p1 + | 1 -> + let! p0 = Arb.generate + let! p1 = Arb.generate |> Gen.filter (fun p -> not (Fun.ApproximateEquals(p, p0, 1E-8))) + let! p2 = Arb.generate |> Gen.filter (fun p -> not (Fun.ApproximateEquals(p, p0, 1E-8)) && not (Fun.ApproximateEquals(p, p1, 1E-8))) + return PathSegment.bezier2 p0 p1 p2 + | 2 -> + let! center = Arb.generate + let! a0 = Arb.generate + let! a1 = Arb.generate |> Gen.filter (fun p -> not (Fun.IsTiny(Vec.AngleBetween(Vec.normalize a0, Vec.normalize p), 1E-5))) + + let! (NormalFloat alpha0) = Arb.generate + let! (NormalFloat dAlpha) = Arb.generate + + let dAlpha = dAlpha % Constant.PiHalf + + return PathSegment.arc alpha0 dAlpha (Ellipse2d(center, a0, a1)) + | _ -> + let! p0 = Arb.generate + let! p1 = Arb.generate |> Gen.filter (fun p -> not (Fun.ApproximateEquals(p, p0, 1E-8))) + let! p2 = Arb.generate |> Gen.filter (fun p -> not (Fun.ApproximateEquals(p, p0, 1E-8)) && not (Fun.ApproximateEquals(p, p1, 1E-8))) + let! p3 = Arb.generate |> Gen.filter (fun p -> not (Fun.ApproximateEquals(p, p0, 1E-8)) && not (Fun.ApproximateEquals(p, p1, 1E-8)) && not (Fun.ApproximateEquals(p, p2, 1E-8))) + return PathSegment.bezier3 p0 p1 p2 p3 + } + } +module Expect = + let inline approxEqualAux< ^a, ^b when (^a or ^b) : (static member ApproximateEquals : ^a * ^a * float -> bool)> (foo : ^ b) (a : 'a) (b : 'a) (eps : float) = + (((^a or ^b) : (static member ApproximateEquals : ^a * ^a * float -> bool) (a, b, eps))) + + + let inline approxEquals a b eps msg = + if not (approxEqualAux Unchecked.defaultof a b eps) then + Expect.equal a b msg + + let inline relativeApproxEquals (a : float) (b : float) eps msg = + + let scale = max (abs a) (abs b) + if not (Fun.IsTiny(scale, eps)) then + let ra = a / scale + let rb = b / scale + + if not (Fun.ApproximateEquals(ra, rb, eps)) then + Expect.equal a b msg + + + +[] +module ExpectoOverrides = + + let config = + { + FsCheckConfig.defaultConfig with + arbitrary = [ typeof ] + maxTest = 100000 + } + + + let testProperty a b = + testPropertyWithConfig config a b diff --git a/src/Tests/Aardvark.Base.Fonts.Tests/PathSegment.fs b/src/Tests/Aardvark.Base.Fonts.Tests/PathSegment.fs new file mode 100644 index 00000000..6305adf5 --- /dev/null +++ b/src/Tests/Aardvark.Base.Fonts.Tests/PathSegment.fs @@ -0,0 +1,251 @@ +module Aardvark.Base.Fonts.Tests.PathSegment + +open Expecto +open Aardvark.Base +open Aardvark.Base.Fonts +open FsCheck + +[] +let smartConstructors = + testList "Fonts.PathSegment" [ + testProperty "tryLine degenerate" <| fun (p1 : V2d) -> + let line = PathSegment.tryLine p1 p1 + Expect.isNone line "impossible to create degenerate line" + + testProperty "tryBezier2 line" <| fun (p0 : V2d) (p2 : V2d) (ZeroOne t) -> + let p1 = lerp p0 p2 t + match PathSegment.tryBezier2 p0 p1 p2 with + | Some (Line(a,b)) -> + Expect.equal a p0 "line start" + Expect.equal b p2 "line end" + | None -> + Expect.equal p0 p2 "degenerate bezier2" + | Some other -> + failwithf "degenerate bezier2 should be a line but is: %A" other + + testProperty "tryBezier2 degenerate" <| fun (p0 : V2d) -> + let bezier2 = PathSegment.tryBezier2 p0 p0 p0 + Expect.isNone bezier2 "impossible to create degenerate bezier2" + + testProperty "tryArcSegment line" <| fun (p0 : V2d) (p2 : V2d) (ZeroOne t) -> + let p1 = lerp p0 p2 t + match PathSegment.tryArcSegment p0 p1 p2 with + | Some (Line(a,b)) -> + Expect.equal a p0 "line start" + Expect.equal b p2 "line end" + | None -> + Expect.equal p0 p2 "degenerate arc" + | Some other -> + failwithf "degenerate arc should be a line but is: %A" other + + testProperty "tryArcSegment degenerate" <| fun (p0 : V2d) -> + let arc = PathSegment.tryArcSegment p0 p0 p0 + Expect.isNone arc "impossible to create degenerate arc" + + testProperty "tryBezier3 with bezier2 points" <| fun (p0 : V2d) (p1 : V2d) (p2 : V2d) -> + let a = lerp p0 p1 (2.0 / 3.0) + let b = lerp p1 p2 (1.0 / 3.0) + + match PathSegment.tryBezier3 p0 a b p2 with + | Some (Line(a, b)) -> + Expect.equal a p0 "start" + Expect.equal b p2 "end" + | None -> + Expect.equal p0 p1 "degenerate bezier3" + Expect.equal p0 p2 "degenerate bezier3" + | Some (Bezier2(a,b,c)) -> + Expect.equal a p0 "start" + Expect.approxEquals b p1 1E-13 "control" + Expect.equal c p2 "end" + | Some other -> + failwithf "degenerate bezier3 should be a bezier2 but is: %A" other + + testProperty "tryBezier3 with line points" <| fun (p0 : V2d) (p3 : V2d) (ZeroOne t1) (ZeroOne t2) -> + + let p1 = lerp p0 p3 (min t1 t2) + let p2 = lerp p0 p3 (max t1 t2) + + match PathSegment.tryBezier3 p0 p1 p2 p3 with + | Some (Line(a, b)) -> + Expect.equal a p0 "start" + Expect.equal b p3 "end" + | None -> + Expect.approxEquals p0 p1 1E-14 "degenerate bezier3" + | Some other -> + failwithf "degenerate bezier3 should be a line but is: %A" other + + testProperty "tryBezier3 degenerate" <| fun (p0 : V2d) -> + let arc = PathSegment.tryBezier3 p0 p0 p0 p0 + Expect.isNone arc "impossible to create degenerate arc" + + testProperty "line degenerate" <| fun (p1 : V2d) -> + let line = try Some (PathSegment.line p1 p1) with _ -> None + Expect.isNone line "impossible to create degenerate line" + + testProperty "bezier2 line" <| fun (p0 : V2d) (p2 : V2d) (ZeroOne t) -> + let p1 = lerp p0 p2 t + + let segment = try Some (PathSegment.bezier2 p0 p1 p2) with _ -> None + + match segment with + | Some (Line(a,b)) -> + Expect.equal a p0 "line start" + Expect.equal b p2 "line end" + | None -> + Expect.equal p0 p2 "degenerate bezier2" + | Some other -> + failwithf "degenerate bezier2 should be a line but is: %A" other + + testProperty "bezier2 degenerate" <| fun (p0 : V2d) -> + let bezier2 = try Some (PathSegment.bezier2 p0 p0 p0) with _ -> None + Expect.isNone bezier2 "impossible to create degenerate bezier2" + + testProperty "arcSegment line" <| fun (p0 : V2d) (p2 : V2d) (ZeroOne t) -> + let p1 = lerp p0 p2 t + let segment = try Some (PathSegment.arcSegment p0 p1 p2) with _ -> None + match segment with + | Some (Line(a,b)) -> + Expect.equal a p0 "line start" + Expect.equal b p2 "line end" + | None -> + Expect.equal p0 p2 "degenerate arc" + | Some other -> + failwithf "degenerate arc should be a line but is: %A" other + + testProperty "arcSegment degenerate" <| fun (p0 : V2d) -> + let arc = try Some (PathSegment.arcSegment p0 p0 p0) with _ -> None + Expect.isNone arc "impossible to create degenerate arc" + + testProperty "bezier3 with bezier2 points" <| fun (p0 : V2d) (p1 : V2d) (p2 : V2d) -> + let a = lerp p0 p1 (2.0 / 3.0) + let b = lerp p1 p2 (1.0 / 3.0) + + let segment = try Some (PathSegment.bezier3 p0 a b p2) with _ -> None + match segment with + | Some (Line(a, b)) -> + Expect.equal a p0 "start" + Expect.equal b p2 "end" + | None -> + Expect.equal p0 p1 "degenerate bezier3" + Expect.equal p0 p2 "degenerate bezier3" + | Some (Bezier2(a,b,c)) -> + Expect.equal a p0 "start" + Expect.approxEquals b p1 1E-13 "control" + Expect.equal c p2 "end" + | Some other -> + failwithf "degenerate bezier3 should be a bezier2 but is: %A" other + + testProperty "bezier3 with line points" <| fun (p0 : V2d) (p3 : V2d) (ZeroOne t1) (ZeroOne t2) -> + + let p1 = lerp p0 p3 (min t1 t2) + let p2 = lerp p0 p3 (max t1 t2) + + let segment = try Some (PathSegment.bezier3 p0 p1 p2 p3) with _ -> None + match segment with + | Some (Line(a, b)) -> + Expect.equal a p0 "start" + Expect.equal b p3 "end" + | None -> + Expect.approxEquals p0 p1 1E-14 "degenerate bezier3" + | Some other -> + failwithf "degenerate bezier3 should be a line but is: %A" other + + testProperty "bezier3 degenerate" <| fun (p0 : V2d) -> + let arc = try Some (PathSegment.bezier3 p0 p0 p0 p0) with _ -> None + Expect.isNone arc "impossible to create degenerate arc" + + + + + ] + +[] +let splitMerge = + testList "Fonts.PathSegment" [ + testProperty "split at 0" <| fun (seg : PathSegment) -> + let (l, r) = PathSegment.split 0.0 seg + Expect.isNone l "left part should be empty" + Expect.equal r (Some seg) "right part should be entire segment" + + testProperty "split at 1" <| fun (seg : PathSegment) -> + let (l, r) = PathSegment.split 1.0 seg + Expect.isNone r "right part should be empty" + Expect.equal l (Some seg) "left part should be entire segment" + + testProperty "tryGetT" <| fun (seg : PathSegment) (ZeroOne t) -> + let pt = PathSegment.point t seg + match PathSegment.tryGetT 1E-7 pt seg with + | Some res -> + Expect.approxEquals res t 1E-8 "tryGetT" + | None -> + failwithf "could not get t for: %A (%A)" t pt + + testProperty "withT0" <| fun (seg : PathSegment) (ZeroOne t) -> + let t = min t 0.95 + let sub = PathSegment.withT0 t seg + Expect.isSome sub "sub should exist" + let sub = sub.Value + + Expect.approxEquals (PathSegment.point t seg) (PathSegment.startPoint sub) 1E-8 "split point equal" + Expect.approxEquals (PathSegment.tangent t seg) (PathSegment.tangent 0.0 sub) 1E-5 "split tangent equal" + Expect.relativeApproxEquals (PathSegment.curvature t seg) (PathSegment.curvature 0.0 sub) 1E-6 "split curvature equal" + Expect.relativeApproxEquals (PathSegment.curvatureDerivative t seg) (PathSegment.curvatureDerivative 0.0 sub) 1E-6 "split curvature derivative equal" + + testProperty "withT1" <| fun (seg : PathSegment) (ZeroOne t) -> + let t = max t 0.05 + let sub = PathSegment.withT1 t seg + Expect.isSome sub "sub should exist" + let sub = sub.Value + + Expect.approxEquals (PathSegment.point t seg) (PathSegment.endPoint sub) 1E-8 "split point equal" + Expect.approxEquals (PathSegment.tangent t seg) (PathSegment.tangent 1.0 sub) 1E-5 "split tangent equal" + Expect.relativeApproxEquals (PathSegment.curvature t seg) (PathSegment.curvature 1.0 sub) 1E-6 "split curvature equal" + Expect.relativeApproxEquals (PathSegment.curvatureDerivative t seg) (PathSegment.curvatureDerivative 1.0 sub) 1E-6 "split curvature derivative equal" + + + + testProperty "split" <| fun (seg : PathSegment) (ZeroOne t) (NonEmptyArray(tests : ZeroOne[]))-> + let t = clamp 1E-2 (1.0 - 1E-2) t + let (l, r) = PathSegment.split t seg + + + Expect.isSome l "left part should exist" + Expect.isSome r "right part should exist" + + match l with + | Some l -> + match r with + | Some r -> + Expect.equal (PathSegment.endPoint l) (PathSegment.startPoint r) "split point equal" + Expect.approxEquals (PathSegment.tangent 1.0 l) (PathSegment.tangent 0.0 r) 1E-5 "split tangent equal" + Expect.relativeApproxEquals (PathSegment.curvature 1.0 l) (PathSegment.curvature 0.0 r) 1E-6 "split curvature equal" + Expect.relativeApproxEquals (PathSegment.curvatureDerivative 1.0 l) (PathSegment.curvatureDerivative 0.0 r) 1E-6 "split curvature derivative equal" + + for (ZeroOne tt) in tests do + let pt = PathSegment.point tt seg + + let tl = PathSegment.tryGetT 1E-7 pt l |> Option.map (fun t -> PathSegment.point t l) + let tr = PathSegment.tryGetT 1E-7 pt r |> Option.map (fun t -> PathSegment.point t r) + + match tl with + | Some pl -> Expect.approxEquals pl pt 1E-7 "point on left part" + | None -> + match tr with + | Some pr -> Expect.approxEquals pr pt 1E-7 "point on left part" + | None -> failwith "different curves" + + + | None -> + () + //Expect.equal l seg "split at end" + | None -> + match r with + | Some r -> + () + //Expect.equal r seg "split at start" + | None -> + failwith "should be impossible (both split parts empty)" + + + + ] \ No newline at end of file diff --git a/src/Tests/Aardvark.Base.Fonts.Tests/Program.fs b/src/Tests/Aardvark.Base.Fonts.Tests/Program.fs new file mode 100644 index 00000000..a9782587 --- /dev/null +++ b/src/Tests/Aardvark.Base.Fonts.Tests/Program.fs @@ -0,0 +1,10 @@ +open Expecto +open Expecto.Impl + +[] +let main args = + let cfg : ExpectoConfig = { ExpectoConfig.defaultConfig with runInParallel = false } + //runTests cfg Aardvark.Base.Fonts.Tests.PathSegment.splitMerge |> ignore + //runTestsWithCLIArgs [] args Aardvark.Rendering.Text.Tests.PathSegment.splitMerge |> ignore + + 0 diff --git a/src/Tests/Aardvark.Base.Fonts.Tests/paket.references b/src/Tests/Aardvark.Base.Fonts.Tests/paket.references new file mode 100644 index 00000000..ab1a5edf --- /dev/null +++ b/src/Tests/Aardvark.Base.Fonts.Tests/paket.references @@ -0,0 +1,12 @@ +group Test + +FsCheck +FsCheck.NUnit +Expecto +Expecto.FsCheck +FSharp.Core +FsUnit +NUnit +NUnit3TestAdapter +Microsoft.NET.Test.Sdk +YoloDev.Expecto.TestSdk \ No newline at end of file diff --git a/src/Tests/Aardvark.Base.Fonts.Tests/test.runsettings b/src/Tests/Aardvark.Base.Fonts.Tests/test.runsettings new file mode 100644 index 00000000..a2610ff9 --- /dev/null +++ b/src/Tests/Aardvark.Base.Fonts.Tests/test.runsettings @@ -0,0 +1,15 @@ + + + + + + + detailed + + + + + + False + + \ No newline at end of file