fixed dependencies
This commit is contained in:
666
vendor/gonum.org/v1/plot/plotter/contour.go
generated
vendored
Normal file
666
vendor/gonum.org/v1/plot/plotter/contour.go
generated
vendored
Normal file
@@ -0,0 +1,666 @@
|
||||
// Copyright ©2015 The Gonum Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package plotter
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"gonum.org/v1/plot"
|
||||
"gonum.org/v1/plot/palette"
|
||||
"gonum.org/v1/plot/vg"
|
||||
"gonum.org/v1/plot/vg/draw"
|
||||
)
|
||||
|
||||
// Contour implements the Plotter interface, drawing
|
||||
// a contour plot of the values in the GridXYZ field.
|
||||
type Contour struct {
|
||||
GridXYZ GridXYZ
|
||||
|
||||
// Levels describes the contour heights to plot.
|
||||
Levels []float64
|
||||
|
||||
// LineStyles is the set of styles for contour
|
||||
// lines. Line styles are are applied to each level
|
||||
// in order, modulo the length of LineStyles.
|
||||
LineStyles []draw.LineStyle
|
||||
|
||||
// Palette is the color palette used to render
|
||||
// the heat map. If Palette is nil or has no
|
||||
// defined color, the Contour LineStyle color
|
||||
// is used.
|
||||
Palette palette.Palette
|
||||
|
||||
// Underflow and Overflow are colors used to draw
|
||||
// contours outside the dynamic range defined
|
||||
// by Min and Max.
|
||||
Underflow color.Color
|
||||
Overflow color.Color
|
||||
|
||||
// Min and Max define the dynamic range of the
|
||||
// heat map.
|
||||
Min, Max float64
|
||||
}
|
||||
|
||||
// NewContour creates as new contour plotter for the given data, using
|
||||
// the provided palette. If levels is nil, contours are generated for
|
||||
// the 0.01, 0.05, 0.25, 0.5, 0.75, 0.95 and 0.99 quantiles.
|
||||
// If g has Min and Max methods that return a float, those returned
|
||||
// values are used to set the respective Contour fields.
|
||||
// If the returned Contour is used when Min is greater than Max, the
|
||||
// Plot method will panic.
|
||||
func NewContour(g GridXYZ, levels []float64, p palette.Palette) *Contour {
|
||||
var min, max float64
|
||||
type minMaxer interface {
|
||||
Min() float64
|
||||
Max() float64
|
||||
}
|
||||
switch g := g.(type) {
|
||||
case minMaxer:
|
||||
min, max = g.Min(), g.Max()
|
||||
default:
|
||||
min, max = math.Inf(1), math.Inf(-1)
|
||||
c, r := g.Dims()
|
||||
for i := 0; i < c; i++ {
|
||||
for j := 0; j < r; j++ {
|
||||
v := g.Z(i, j)
|
||||
if math.IsNaN(v) {
|
||||
continue
|
||||
}
|
||||
min = math.Min(min, v)
|
||||
max = math.Max(max, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(levels) == 0 {
|
||||
levels = quantilesR7(g, defaultQuantiles)
|
||||
}
|
||||
|
||||
return &Contour{
|
||||
GridXYZ: g,
|
||||
Levels: levels,
|
||||
LineStyles: []draw.LineStyle{DefaultLineStyle},
|
||||
Palette: p,
|
||||
Min: min,
|
||||
Max: max,
|
||||
}
|
||||
}
|
||||
|
||||
// Default quantiles for case where levels is not explicitly set.
|
||||
var defaultQuantiles = []float64{0.01, 0.05, 0.25, 0.5, 0.75, 0.95, 0.99}
|
||||
|
||||
// quantilesR7 returns the pth quantiles of the data in g according to the R-7 method.
|
||||
// http://en.wikipedia.org/wiki/Quantile#Estimating_the_quantiles_of_a_population
|
||||
func quantilesR7(g GridXYZ, p []float64) []float64 {
|
||||
c, r := g.Dims()
|
||||
data := make([]float64, 0, c*r)
|
||||
for i := 0; i < c; i++ {
|
||||
for j := 0; j < r; j++ {
|
||||
if v := g.Z(i, j); !math.IsNaN(v) {
|
||||
data = append(data, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Float64s(data)
|
||||
v := make([]float64, len(p))
|
||||
for j, q := range p {
|
||||
if q == 1 {
|
||||
v[j] = data[len(data)-1]
|
||||
}
|
||||
h := float64(len(data)-1) * q
|
||||
i := int(h)
|
||||
v[j] = data[i] + (h-math.Floor(h))*(data[i+1]-data[i])
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// naive is a debugging constant. If true, Plot performs no contour path
|
||||
// reconstruction, instead rendering each path segment individually.
|
||||
const naive = false
|
||||
|
||||
// Plot implements the Plot method of the plot.Plotter interface.
|
||||
func (h *Contour) Plot(c draw.Canvas, plt *plot.Plot) {
|
||||
if h.Min > h.Max {
|
||||
panic("contour: invalid Z range: min greater than max")
|
||||
}
|
||||
|
||||
if naive {
|
||||
h.naivePlot(c, plt)
|
||||
return
|
||||
}
|
||||
|
||||
var pal []color.Color
|
||||
if h.Palette != nil {
|
||||
pal = h.Palette.Colors()
|
||||
}
|
||||
|
||||
trX, trY := plt.Transforms(&c)
|
||||
|
||||
// Collate contour paths and draw them.
|
||||
//
|
||||
// The alternative naive approach is to draw each line segment as
|
||||
// conrec returns it. The integrated path approach allows graphical
|
||||
// optimisations and is necessary for contour fill shading.
|
||||
cp := contourPaths(h.GridXYZ, h.Levels, trX, trY)
|
||||
|
||||
// ps is a palette scaling factor to scale the palette uniformly
|
||||
// across the given levels. This enables a discordance between the
|
||||
// number of colours and the number of levels. Sorting is not
|
||||
// necessary since contourPaths sorts the levels as a side effect.
|
||||
ps := float64(len(pal)-1) / (h.Levels[len(h.Levels)-1] - h.Levels[0])
|
||||
if len(h.Levels) == 1 {
|
||||
ps = 0
|
||||
}
|
||||
|
||||
for i, z := range h.Levels {
|
||||
if math.IsNaN(z) {
|
||||
continue
|
||||
}
|
||||
for _, pa := range cp[z] {
|
||||
if isLoop(pa) {
|
||||
pa.Close()
|
||||
}
|
||||
|
||||
style := h.LineStyles[i%len(h.LineStyles)]
|
||||
var col color.Color
|
||||
switch {
|
||||
case z < h.Min:
|
||||
col = h.Underflow
|
||||
case z > h.Max:
|
||||
col = h.Overflow
|
||||
case len(pal) == 0:
|
||||
col = style.Color
|
||||
default:
|
||||
col = pal[int((z-h.Levels[0])*ps+0.5)] // Apply palette scaling.
|
||||
}
|
||||
if col != nil && style.Width != 0 {
|
||||
c.SetLineStyle(style)
|
||||
c.SetColor(col)
|
||||
c.Stroke(pa)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// naivePlot implements a naive rendering approach for contours.
|
||||
// It is here as a debugging mode since it simply draws line segments
|
||||
// generated by conrec without further computation.
|
||||
func (h *Contour) naivePlot(c draw.Canvas, plt *plot.Plot) {
|
||||
var pal []color.Color
|
||||
if h.Palette != nil {
|
||||
pal = h.Palette.Colors()
|
||||
}
|
||||
|
||||
trX, trY := plt.Transforms(&c)
|
||||
|
||||
// Sort levels prior to palette scaling since we can't depend on
|
||||
// sorting as a side effect from calling contourPaths.
|
||||
sort.Float64s(h.Levels)
|
||||
// ps is a palette scaling factor to scale the palette uniformly
|
||||
// across the given levels. This enables a discordance between the
|
||||
// number of colours and the number of levels.
|
||||
ps := float64(len(pal)-1) / (h.Levels[len(h.Levels)-1] - h.Levels[0])
|
||||
if len(h.Levels) == 1 {
|
||||
ps = 0
|
||||
}
|
||||
|
||||
levelMap := make(map[float64]int)
|
||||
for i, z := range h.Levels {
|
||||
levelMap[z] = i
|
||||
}
|
||||
|
||||
// Draw each line segment as conrec generates it.
|
||||
var pa vg.Path
|
||||
conrec(h.GridXYZ, h.Levels, func(_, _ int, l line, z float64) {
|
||||
if math.IsNaN(z) {
|
||||
return
|
||||
}
|
||||
|
||||
pa = pa[:0]
|
||||
|
||||
x1, y1 := trX(l.p1.X), trY(l.p1.Y)
|
||||
x2, y2 := trX(l.p2.X), trY(l.p2.Y)
|
||||
|
||||
pt1 := vg.Point{X: x1, Y: y1}
|
||||
pt2 := vg.Point{X: x2, Y: y2}
|
||||
if !c.Contains(pt1) || !c.Contains(pt2) {
|
||||
return
|
||||
}
|
||||
|
||||
pa.Move(pt1)
|
||||
pa.Line(pt2)
|
||||
pa.Close()
|
||||
|
||||
style := h.LineStyles[levelMap[z]%len(h.LineStyles)]
|
||||
var col color.Color
|
||||
switch {
|
||||
case z < h.Min:
|
||||
col = h.Underflow
|
||||
case z > h.Max:
|
||||
col = h.Overflow
|
||||
case len(pal) == 0:
|
||||
col = style.Color
|
||||
default:
|
||||
col = pal[int((z-h.Levels[0])*ps+0.5)] // Apply palette scaling.
|
||||
}
|
||||
if col != nil && style.Width != 0 {
|
||||
c.SetLineStyle(style)
|
||||
c.SetColor(col)
|
||||
c.Stroke(pa)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// DataRange implements the DataRange method
|
||||
// of the plot.DataRanger interface.
|
||||
func (h *Contour) DataRange() (xmin, xmax, ymin, ymax float64) {
|
||||
c, r := h.GridXYZ.Dims()
|
||||
return h.GridXYZ.X(0), h.GridXYZ.X(c - 1), h.GridXYZ.Y(0), h.GridXYZ.Y(r - 1)
|
||||
}
|
||||
|
||||
// GlyphBoxes implements the GlyphBoxes method
|
||||
// of the plot.GlyphBoxer interface.
|
||||
func (h *Contour) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
|
||||
c, r := h.GridXYZ.Dims()
|
||||
b := make([]plot.GlyphBox, 0, r*c)
|
||||
for i := 0; i < c; i++ {
|
||||
for j := 0; j < r; j++ {
|
||||
b = append(b, plot.GlyphBox{
|
||||
X: plt.X.Norm(h.GridXYZ.X(i)),
|
||||
Y: plt.Y.Norm(h.GridXYZ.Y(j)),
|
||||
Rectangle: vg.Rectangle{
|
||||
Min: vg.Point{X: -2.5, Y: -2.5},
|
||||
Max: vg.Point{X: +2.5, Y: +2.5},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// isLoop returns true iff a vg.Path is a closed loop.
|
||||
func isLoop(p vg.Path) bool {
|
||||
s := p[0]
|
||||
e := p[len(p)-1]
|
||||
return s.Pos == e.Pos
|
||||
}
|
||||
|
||||
// contourPaths returns a collection of vg.Paths describing contour lines based
|
||||
// on the input data in m cut at the given levels. The trX and trY function
|
||||
// are coordinate transforms. The returned map contains slices of paths keyed
|
||||
// on the value of the contour level. contouPaths sorts levels ascending as a
|
||||
// side effect.
|
||||
func contourPaths(m GridXYZ, levels []float64, trX, trY func(float64) vg.Length) map[float64][]vg.Path {
|
||||
sort.Float64s(levels)
|
||||
|
||||
ends := make(map[float64]endMap)
|
||||
conts := make(contourSet)
|
||||
conrec(m, levels, func(_, _ int, l line, z float64) {
|
||||
paths(l, z, ends, conts)
|
||||
})
|
||||
ends = nil
|
||||
|
||||
// TODO(kortschak): Check that all non-loop paths have
|
||||
// both ends at boundary. If any end is not at a boundary
|
||||
// it may have a partner near by. Find this partner and join
|
||||
// the two conts by merging the near by ends at the mean
|
||||
// location. This operation is done level by level to ensure
|
||||
// close contours of different heights are not joined.
|
||||
// A partner should be a float error different end, but I
|
||||
// suspect that is is possible for a bi- or higher order
|
||||
// furcation so it may be that the path ends at middle node
|
||||
// of another path. This needs to be investigated.
|
||||
|
||||
// Excise loops from crossed paths.
|
||||
for c := range conts {
|
||||
// Always try to do quick excision in production if possible.
|
||||
c.exciseLoops(conts, true)
|
||||
}
|
||||
|
||||
// Build vg.Paths.
|
||||
paths := make(map[float64][]vg.Path)
|
||||
for c := range conts {
|
||||
paths[c.z] = append(paths[c.z], c.path(trX, trY))
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
// contourSet hold a working collection of contours.
|
||||
type contourSet map[*contour]struct{}
|
||||
|
||||
// endMap holds a working collection of available ends.
|
||||
type endMap map[point]*contour
|
||||
|
||||
// paths extends a conrecLine function to build a set of contours that represent
|
||||
// paths along contour lines. It is used as the engine for a closure where ends
|
||||
// and conts are closed around in a conrecLine function, and l and z are the
|
||||
// line and height values provided by conrec. At the end of a conrec call,
|
||||
// conts will contain a map keyed on the set of paths.
|
||||
func paths(l line, z float64, ends map[float64]endMap, conts contourSet) {
|
||||
zEnds, ok := ends[z]
|
||||
if !ok {
|
||||
zEnds = make(endMap)
|
||||
ends[z] = zEnds
|
||||
c := newContour(l, z)
|
||||
zEnds[l.p1] = c
|
||||
zEnds[l.p2] = c
|
||||
conts[c] = struct{}{}
|
||||
return
|
||||
}
|
||||
|
||||
c1, ok1 := zEnds[l.p1]
|
||||
c2, ok2 := zEnds[l.p2]
|
||||
|
||||
// New segment.
|
||||
if !ok1 && !ok2 {
|
||||
c := newContour(l, z)
|
||||
zEnds[l.p1] = c
|
||||
zEnds[l.p2] = c
|
||||
conts[c] = struct{}{}
|
||||
return
|
||||
}
|
||||
|
||||
if ok1 {
|
||||
// Add l.p2 to end of l.p1's contour.
|
||||
if !c1.extend(l, zEnds) {
|
||||
panic("internal link")
|
||||
}
|
||||
} else if ok2 {
|
||||
// Add l.p1 to end of l.p2's contour.
|
||||
if !c2.extend(l, zEnds) {
|
||||
panic("internal link")
|
||||
}
|
||||
}
|
||||
|
||||
if c1 == c2 {
|
||||
return
|
||||
}
|
||||
|
||||
// Join conts.
|
||||
if ok1 && ok2 {
|
||||
if !c1.connect(c2, zEnds) {
|
||||
panic("internal link")
|
||||
}
|
||||
delete(conts, c2)
|
||||
}
|
||||
}
|
||||
|
||||
// path is a set of points forming a path.
|
||||
type path []point
|
||||
|
||||
// contour holds a set of point lying sequentially along a contour line
|
||||
// at height z.
|
||||
type contour struct {
|
||||
z float64
|
||||
|
||||
// backward and forward must each always have at least one entry.
|
||||
backward path
|
||||
forward path
|
||||
}
|
||||
|
||||
// newContour returns a contour starting with the end points of l for the
|
||||
// height z.
|
||||
func newContour(l line, z float64) *contour {
|
||||
return &contour{z: z, forward: path{l.p1}, backward: path{l.p2}}
|
||||
}
|
||||
|
||||
func (c *contour) path(trX, trY func(float64) vg.Length) vg.Path {
|
||||
var pa vg.Path
|
||||
p := c.front()
|
||||
pa.Move(vg.Point{X: trX(p.X), Y: trY(p.Y)})
|
||||
for i := len(c.backward) - 2; i >= 0; i-- {
|
||||
p = c.backward[i]
|
||||
pa.Line(vg.Point{X: trX(p.X), Y: trY(p.Y)})
|
||||
}
|
||||
for _, p := range c.forward {
|
||||
pa.Line(vg.Point{X: trX(p.X), Y: trY(p.Y)})
|
||||
}
|
||||
|
||||
return pa
|
||||
}
|
||||
|
||||
// front returns the first point in the contour.
|
||||
func (c *contour) front() point { return c.backward[len(c.backward)-1] }
|
||||
|
||||
// back returns the last point in the contour
|
||||
func (c *contour) back() point { return c.forward[len(c.forward)-1] }
|
||||
|
||||
// extend adds the line l to the contour, updating the ends map. It returns
|
||||
// a boolean indicating whether the extension was successful.
|
||||
func (c *contour) extend(l line, ends endMap) (ok bool) {
|
||||
switch c.front() {
|
||||
case l.p1:
|
||||
c.backward = append(c.backward, l.p2)
|
||||
delete(ends, l.p1)
|
||||
ends[l.p2] = c
|
||||
return true
|
||||
case l.p2:
|
||||
c.backward = append(c.backward, l.p1)
|
||||
delete(ends, l.p2)
|
||||
ends[l.p1] = c
|
||||
return true
|
||||
}
|
||||
|
||||
switch c.back() {
|
||||
case l.p1:
|
||||
c.forward = append(c.forward, l.p2)
|
||||
delete(ends, l.p1)
|
||||
ends[l.p2] = c
|
||||
return true
|
||||
case l.p2:
|
||||
c.forward = append(c.forward, l.p1)
|
||||
delete(ends, l.p2)
|
||||
ends[l.p1] = c
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// reverse reverses the order of the point in a path and returns it.
|
||||
func (p path) reverse() path {
|
||||
for i, j := 0, len(p)-1; i < j; i, j = i+1, j-1 {
|
||||
p[i], p[j] = p[j], p[i]
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// connect connects the contour b with the receiver, updating the ends map.
|
||||
// It returns a boolean indicating whether the connection was successful.
|
||||
func (c *contour) connect(b *contour, ends endMap) (ok bool) {
|
||||
switch c.front() {
|
||||
case b.front():
|
||||
delete(ends, c.front())
|
||||
ends[b.back()] = c
|
||||
c.backward = append(c.backward, b.backward.reverse()[1:]...)
|
||||
c.backward = append(c.backward, b.forward...)
|
||||
return true
|
||||
case b.back():
|
||||
delete(ends, c.front())
|
||||
ends[b.front()] = c
|
||||
c.backward = append(c.backward, b.forward.reverse()[1:]...)
|
||||
c.backward = append(c.backward, b.backward...)
|
||||
return true
|
||||
}
|
||||
|
||||
switch c.back() {
|
||||
case b.front():
|
||||
delete(ends, c.back())
|
||||
ends[b.back()] = c
|
||||
c.forward = append(c.forward, b.backward.reverse()[1:]...)
|
||||
c.forward = append(c.forward, b.forward...)
|
||||
return true
|
||||
case b.back():
|
||||
delete(ends, c.back())
|
||||
ends[b.front()] = c
|
||||
c.forward = append(c.forward, b.forward.reverse()[1:]...)
|
||||
c.forward = append(c.forward, b.backward...)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// exciseLoops finds loops within the contour that do not include the
|
||||
// start and end. Loops are removed from the contour and added to the
|
||||
// contour set. Loop detection is performed by Johnson's algorithm for
|
||||
// finding elementary cycles.
|
||||
func (c *contour) exciseLoops(conts contourSet, quick bool) {
|
||||
if quick {
|
||||
// Find cases we can guarantee don't need
|
||||
// a complete analysis.
|
||||
seen := make(map[point]struct{})
|
||||
var crossOvers int
|
||||
for _, p := range c.backward {
|
||||
if _, ok := seen[p]; ok {
|
||||
crossOvers++
|
||||
}
|
||||
seen[p] = struct{}{}
|
||||
}
|
||||
for _, p := range c.forward[:len(c.forward)-1] {
|
||||
if _, ok := seen[p]; ok {
|
||||
crossOvers++
|
||||
}
|
||||
seen[p] = struct{}{}
|
||||
}
|
||||
switch crossOvers {
|
||||
case 0:
|
||||
return
|
||||
case 1:
|
||||
c.exciseQuick(conts)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
wp := append(c.backward.reverse(), c.forward...)
|
||||
g := graphFrom(wp)
|
||||
cycles := cyclesIn(g)
|
||||
if len(cycles) == 0 {
|
||||
// No further work to do but clean up after ourselves.
|
||||
// We should not have reached here.
|
||||
c.backward.reverse()
|
||||
return
|
||||
}
|
||||
delete(conts, c)
|
||||
|
||||
// Put loops into the contour set.
|
||||
for _, cyc := range cycles {
|
||||
loop := wp.subpath(cyc)
|
||||
conts[&contour{
|
||||
z: c.z,
|
||||
backward: loop[:1:1],
|
||||
forward: loop[1:],
|
||||
}] = struct{}{}
|
||||
}
|
||||
|
||||
// Find non-loop paths and keep them.
|
||||
g.remove(cycles)
|
||||
paths := wp.linearPathsIn(g)
|
||||
for _, p := range paths {
|
||||
conts[&contour{
|
||||
z: c.z,
|
||||
backward: p[:1:1],
|
||||
forward: p[1:],
|
||||
}] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// graphFrom returns a graph representing the point path p.
|
||||
func graphFrom(p path) graph {
|
||||
g := make([]set, len(p))
|
||||
seen := make(map[point]int)
|
||||
for i, v := range p {
|
||||
if _, ok := seen[v]; !ok {
|
||||
seen[v] = i
|
||||
}
|
||||
}
|
||||
|
||||
for i, v := range p {
|
||||
e, ok := seen[v]
|
||||
if ok && g[e] == nil {
|
||||
g[e] = make(set)
|
||||
}
|
||||
if i < len(p)-1 {
|
||||
g[e][seen[p[i+1]]] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
// subpath returns a subpath given the slice of point indices
|
||||
// into the path.
|
||||
func (p path) subpath(i []int) path {
|
||||
pa := make(path, 0, len(i))
|
||||
for _, n := range i {
|
||||
pa = append(pa, p[n])
|
||||
}
|
||||
return pa
|
||||
}
|
||||
|
||||
// linearPathsIn returns the linear paths in g created from p.
|
||||
// If g contains any cycles linearPaths will panic.
|
||||
func (p path) linearPathsIn(g graph) []path {
|
||||
var pa []path
|
||||
|
||||
var u int
|
||||
for u < len(g) {
|
||||
for ; u < len(g) && len(g[u]) == 0; u++ {
|
||||
}
|
||||
if u == len(g) {
|
||||
return pa
|
||||
}
|
||||
var curr path
|
||||
for {
|
||||
if len(g[u]) == 0 {
|
||||
curr = append(curr, p[u])
|
||||
pa = append(pa, curr)
|
||||
if u == len(g)-1 {
|
||||
return pa
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(g[u]) > 1 {
|
||||
panic("contour: not a linear path")
|
||||
}
|
||||
for v := range g[u] {
|
||||
curr = append(curr, p[u])
|
||||
u = v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pa
|
||||
}
|
||||
|
||||
// exciseQuick is a heuristic approach to loop excision. It does not
|
||||
// correctly identify loops in all cases, but those cases are likely
|
||||
// to be rare.
|
||||
func (c *contour) exciseQuick(conts contourSet) {
|
||||
wp := append(c.backward.reverse(), c.forward...)
|
||||
seen := make(map[point]int)
|
||||
for j := 0; j < len(wp); {
|
||||
p := wp[j]
|
||||
if i, ok := seen[p]; ok && p != wp[0] && p != wp[len(wp)-1] {
|
||||
conts[&contour{
|
||||
z: c.z,
|
||||
backward: path{wp[i]},
|
||||
forward: append(path(nil), wp[i+1:j+1]...),
|
||||
}] = struct{}{}
|
||||
wp = append(wp[:i], wp[j:]...)
|
||||
j = i + 1
|
||||
} else {
|
||||
seen[p] = j
|
||||
j++
|
||||
}
|
||||
}
|
||||
c.backward = c.backward[:1]
|
||||
c.backward[0] = wp[0]
|
||||
c.forward = wp[1:]
|
||||
}
|
||||
Reference in New Issue
Block a user