Mercurial > gemma
view pkg/octree/triangulation.go @ 3621:2893ee8ce06f single-beam
concave hulls for single beam scans ... WIP.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Wed, 05 Jun 2019 16:34:52 +0200 |
parents | e1021fd60190 |
children | 1973fa69b2bb |
line wrap: on
line source
// Copyright (C) 2018 Michael Fogleman // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package octree import ( "fmt" "log" "math" "os" svg "github.com/ajstarks/svgo" ) type Triangulation struct { Points []Vertex ConvexHull []Vertex Triangles []int32 Halfedges []int32 } // Triangulate returns a Delaunay triangulation of the provided points. func Triangulate(points []Vertex) (*Triangulation, error) { t := newTriangulator(points) err := t.triangulate() return &Triangulation{points, t.convexHull(), t.triangles, t.halfedges}, err } func (t *Triangulation) ConcaveHull(tooLong float64) []int32 { tooLong *= tooLong var candidates []int32 for i, num := 0, len(t.Triangles)/3; i < num; i++ { idx := i * 3 var max float64 vs := t.Triangles[idx : idx+3] for j, vj := range vs { k := (j + 1) % 3 if l := t.Points[vj].SquaredDistance2D(t.Points[vs[k]]); l > max { max = l } } if max > tooLong { candidates = append(candidates, int32(i)) } } removed := map[int32]bool{} isBorder := func(n int32) bool { n *= 3 for i := int32(0); i < 3; i++ { e := n + i if o := t.Halfedges[e]; o < 0 || removed[o/3] { return true } } return false } var newCandidates []int32 for len(candidates) > 0 { log.Printf("info: candidates: %d\n", len(candidates)) oldRemoved := len(removed) for _, i := range candidates { if isBorder(i) { removed[i] = true } else { newCandidates = append(newCandidates, i) } } if oldRemoved == len(removed) { break } candidates = newCandidates newCandidates = newCandidates[:0] } log.Printf("info: candidates left: %d\n", len(candidates)) log.Printf("info: triangles: %d\n", len(t.Triangles)/3) log.Printf("info: triangles to remove: %d\n", len(removed)) var edges [][2]int32 for i, num := int32(0), int32(len(t.Triangles)/3); i < num; i++ { if removed[i] { continue } n := i * 3 for j := int32(0); j < 3; j++ { e := n + j if t.Halfedges[e] < 0 || removed[e/3] { edges = append(edges, [2]int32{ t.Triangles[e], t.Triangles[n+(j+1)%3], }) } } } for i := range edges { fmt.Printf("%d - %d\n", edges[i][0], edges[i][1]) } log.Printf("num of border triangles: %d\n", len(edges)) log.Printf("len convex hull: %d\n", len(t.ConvexHull)) var ( minX float64 = math.MaxFloat64 minY float64 = math.MaxFloat64 maxX float64 = -math.MaxFloat64 maxY float64 = -math.MaxFloat64 ) for i := range edges { p0 := t.Points[edges[i][0]] p1 := t.Points[edges[i][1]] minX = math.Min(p0.X, minX) maxX = math.Max(p0.X, maxX) minY = math.Min(p0.Y, minY) maxY = math.Max(p0.Y, maxY) minX = math.Min(p1.X, minX) maxX = math.Max(p1.X, maxX) minY = math.Min(p1.Y, minY) maxY = math.Max(p1.Y, maxY) } linear := func(x1, y1, x2, y2 float64) func(float64) float64 { // y1 = x1*m + b // y2 = x2*m + b // b = y1 - x1*m // y1-y2 = (x1-x2)*m // m = (y1-y2)/(x1-x2) for x1 != x2 if x1 == x2 { return func(float64) float64 { return (y1 + y2) * 0.5 } } m := (y1 - y2) / (x1 - x2) b := y1 - x1*m return func(x float64) float64 { return m*x + b } } xf := linear(minX, 0, maxX, 1499) yf := linear(minY, 0, maxY, 1499) f, err := os.Create("/tmp/hull.svg") if err == nil { canvas := svg.New(f) canvas.Start(1500, 1500) for i := range edges { p0 := t.Points[edges[i][0]] p1 := t.Points[edges[i][1]] x1 := int(math.Floor(xf(p0.X))) y1 := int(math.Floor(yf(p0.Y))) x2 := int(math.Floor(xf(p1.X))) y2 := int(math.Floor(yf(p1.Y))) canvas.Line(x1, y1, x2, y2, "stroke:black") } canvas.End() f.Close() } return nil } func (t *Triangulation) TriangleSlices() [][]int32 { sl := make([][]int32, len(t.Triangles)/3) var j int for i := range sl { sl[i] = t.Triangles[j : j+3] j += 3 } return sl } func (t *Triangulation) Tin() *Tin { min := Vertex{math.MaxFloat64, math.MaxFloat64, math.MaxFloat64} max := Vertex{-math.MaxFloat64, -math.MaxFloat64, -math.MaxFloat64} vertices := t.Points for _, v := range vertices { min.Minimize(v) max.Maximize(v) } return &Tin{ Vertices: vertices, Triangles: t.TriangleSlices(), Min: min, Max: max, } } func (t *Triangulation) area() float64 { var result float64 points := t.Points ts := t.Triangles for i := 0; i < len(ts); i += 3 { p0 := points[ts[i+0]] p1 := points[ts[i+1]] p2 := points[ts[i+2]] result += area(p0, p1, p2) } return result / 2 } // Validate performs several sanity checks on the Triangulation to check for // potential errors. Returns nil if no issues were found. You normally // shouldn't need to call this function but it can be useful for debugging. func (t *Triangulation) Validate() error { // verify halfedges for i1, i2 := range t.Halfedges { if i1 != -1 && t.Halfedges[i1] != i2 { return fmt.Errorf("invalid halfedge connection") } if i2 != -1 && t.Halfedges[i2] != int32(i1) { return fmt.Errorf("invalid halfedge connection") } } // verify convex hull area vs sum of triangle areas hull1 := t.ConvexHull hull2 := ConvexHull(t.Points) area1 := polygonArea(hull1) area2 := polygonArea(hull2) area3 := t.area() if math.Abs(area1-area2) > 1e-9 || math.Abs(area1-area3) > 1e-9 { return fmt.Errorf("hull areas disagree: %f, %f, %f", area1, area2, area3) } // verify convex hull perimeter perimeter1 := polygonPerimeter(hull1) perimeter2 := polygonPerimeter(hull2) if math.Abs(perimeter1-perimeter2) > 1e-9 { return fmt.Errorf("hull perimeters disagree: %f, %f", perimeter1, perimeter2) } return nil }