Mercurial > gemma
diff pkg/mesh/triangulator.go @ 4827:f4abfd0ee8ad remove-octree-debris
Renamed octree package to mesh as there is no octree any more.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Tue, 05 Nov 2019 14:30:22 +0100 |
parents | pkg/octree/triangulator.go@d12c2f4d3483 |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/mesh/triangulator.go Tue Nov 05 14:30:22 2019 +0100 @@ -0,0 +1,375 @@ +// 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 mesh + +import ( + "errors" + "math" + "sort" +) + +type triangulator struct { + points []Vertex + squaredDistances []float64 + ids []int32 + center Vertex + triangles []int32 + halfedges []int32 + trianglesLen int + hull *node + hash []*node +} + +func newTriangulator(points []Vertex) *triangulator { + return &triangulator{points: points} +} + +// sorting a triangulator sorts the `ids` such that the referenced points +// are in order by their distance to `center` +func (tri *triangulator) Len() int { + return len(tri.points) +} + +func (tri *triangulator) Swap(i, j int) { + tri.ids[i], tri.ids[j] = tri.ids[j], tri.ids[i] +} + +func (tri *triangulator) Less(i, j int) bool { + d1 := tri.squaredDistances[tri.ids[i]] + d2 := tri.squaredDistances[tri.ids[j]] + if d1 != d2 { + return d1 < d2 + } + p1 := tri.points[tri.ids[i]] + p2 := tri.points[tri.ids[j]] + if p1.X != p2.X { + return p1.X < p2.X + } + return p1.Y < p2.Y +} + +func (tri *triangulator) triangulate() error { + points := tri.points + + n := len(points) + if n == 0 { + return nil + } + + tri.ids = make([]int32, n) + + // compute bounds + x0 := points[0].X + y0 := points[0].Y + x1 := points[0].X + y1 := points[0].Y + for i, p := range points { + if p.X < x0 { + x0 = p.X + } + if p.X > x1 { + x1 = p.X + } + if p.Y < y0 { + y0 = p.Y + } + if p.Y > y1 { + y1 = p.Y + } + tri.ids[i] = int32(i) + } + + var i0, i1, i2 int32 + + // pick a seed point close to midpoint + m := Vertex{X: (x0 + x1) / 2, Y: (y0 + y1) / 2} + minDist := infinity + for i, p := range points { + d := p.SquaredDistance2D(m) + if d < minDist { + i0 = int32(i) + minDist = d + } + } + + // find point closest to seed point + minDist = infinity + for i, p := range points { + if int32(i) == i0 { + continue + } + d := p.SquaredDistance2D(points[i0]) + if d > 0 && d < minDist { + i1 = int32(i) + minDist = d + } + } + + // find the third point which forms the smallest circumcircle + minRadius := infinity + for i, p := range points { + if int32(i) == i0 || int32(i) == i1 { + continue + } + r := circumradius(points[i0], points[i1], p) + if r < minRadius { + i2 = int32(i) + minRadius = r + } + } + if minRadius == infinity { + return errors.New("no delaunay triangulation exists for this input") + } + + // swap the order of the seed points for counter-clockwise orientation + if area(points[i0], points[i1], points[i2]) < 0 { + i1, i2 = i2, i1 + } + + tri.center = circumcenter(points[i0], points[i1], points[i2]) + + // sort the points by distance from the seed triangle circumcenter + tri.squaredDistances = make([]float64, n) + for i, p := range points { + tri.squaredDistances[i] = p.SquaredDistance2D(tri.center) + } + sort.Sort(tri) + + // initialize a hash table for storing edges of the advancing convex hull + hashSize := int(math.Ceil(math.Sqrt(float64(n)))) + tri.hash = make([]*node, hashSize) + + // initialize a circular doubly-linked list that will hold an advancing convex hull + nodes := make([]node, n) + + e := newNode(nodes, i0, nil) + e.t = 0 + tri.hashEdge(e) + + e = newNode(nodes, i1, e) + e.t = 1 + tri.hashEdge(e) + + e = newNode(nodes, i2, e) + e.t = 2 + tri.hashEdge(e) + + tri.hull = e + + maxTriangles := 2*n - 5 + tri.triangles = make([]int32, maxTriangles*3) + tri.halfedges = make([]int32, maxTriangles*3) + + tri.addTriangle(i0, i1, i2, -1, -1, -1) + + pp := Vertex{X: infinity, Y: infinity} + for k := 0; k < n; k++ { + i := tri.ids[k] + p := points[i] + + // skip nearly-duplicate points + if p.SquaredDistance2D(pp) < eps { + continue + } + pp = p + + // skip seed triangle points + if i == int32(i0) || i == int32(i1) || i == int32(i2) { + continue + } + + // find a visible edge on the convex hull using edge hash + var start *node + key := tri.hashKey(p) + for j := 0; j < len(tri.hash); j++ { + start = tri.hash[key] + if start != nil && start.i >= 0 { + break + } + key++ + if key >= len(tri.hash) { + key = 0 + } + } + start = start.prev + + e := start + for area(p, points[e.i], points[e.next.i]) >= 0 { + e = e.next + if e == start { + e = nil + break + } + } + if e == nil { + // likely a near-duplicate point; skip it + continue + } + walkBack := e == start + + // add the first triangle from the point + t := tri.addTriangle(e.i, i, e.next.i, -1, -1, e.t) + e.t = t // keep track of boundary triangles on the hull + e = newNode(nodes, i, e) + + // recursively flip triangles from the point until they satisfy the Delaunay condition + e.t = tri.legalize(t + 2) + + // walk forward through the hull, adding more triangles and flipping recursively + q := e.next + for area(p, points[q.i], points[q.next.i]) < 0 { + t = tri.addTriangle(q.i, i, q.next.i, q.prev.t, -1, q.t) + q.prev.t = tri.legalize(t + 2) + tri.hull = q.remove() + q = q.next + } + + if walkBack { + // walk backward from the other side, adding more triangles and flipping + q := e.prev + for area(p, points[q.prev.i], points[q.i]) < 0 { + t = tri.addTriangle(q.prev.i, i, q.i, -1, q.t, q.prev.t) + tri.legalize(t + 2) + q.prev.t = t + tri.hull = q.remove() + q = q.prev + } + } + + // save the two new edges in the hash table + tri.hashEdge(e) + tri.hashEdge(e.prev) + } + + tri.triangles = tri.triangles[:tri.trianglesLen] + tri.halfedges = tri.halfedges[:tri.trianglesLen] + + return nil +} + +func (tri *triangulator) hashKey(point Vertex) int { + d := point.Sub2D(tri.center) + return int(pseudoAngle(d.X, d.Y) * float64(len(tri.hash))) +} + +func (tri *triangulator) hashEdge(e *node) { + tri.hash[tri.hashKey(tri.points[e.i])] = e +} + +func (tri *triangulator) addTriangle(i0, i1, i2, a, b, c int32) int32 { + i := int32(tri.trianglesLen) + tri.triangles[i] = i0 + tri.triangles[i+1] = i1 + tri.triangles[i+2] = i2 + tri.link(i, a) + tri.link(i+1, b) + tri.link(i+2, c) + tri.trianglesLen += 3 + return i +} + +func (tri *triangulator) link(a, b int32) { + tri.halfedges[a] = b + if b >= 0 { + tri.halfedges[b] = a + } +} + +func (tri *triangulator) legalize(a int32) int32 { + // if the pair of triangles doesn't satisfy the Delaunay condition + // (p1 is inside the circumcircle of [p0, pl, pr]), flip them, + // then do the same check/flip recursively for the new pair of triangles + // + // pl pl + // /||\ / \ + // al/ || \bl al/ \a + // / || \ / \ + // / a||b \ flip /___ar___\ + // p0\ || /p1 => p0\---bl---/p1 + // \ || / \ / + // ar\ || /br b\ /br + // \||/ \ / + // pr pr + + b := tri.halfedges[a] + + a0 := a - a%3 + b0 := b - b%3 + + al := a0 + (a+1)%3 + ar := a0 + (a+2)%3 + bl := b0 + (b+2)%3 + + if b < 0 { + return ar + } + + p0 := tri.triangles[ar] + pr := tri.triangles[a] + pl := tri.triangles[al] + p1 := tri.triangles[bl] + + illegal := inCircle(tri.points[p0], tri.points[pr], tri.points[pl], tri.points[p1]) + + if illegal { + tri.triangles[a] = p1 + tri.triangles[b] = p0 + + // edge swapped on the other side of the hull (rare) + // fix the halfedge reference + if tri.halfedges[bl] == -1 { + e := tri.hull + for { + if e.t == bl { + e.t = a + break + } + e = e.next + if e == tri.hull { + break + } + } + } + + tri.link(a, tri.halfedges[bl]) + tri.link(b, tri.halfedges[ar]) + tri.link(ar, bl) + + br := b0 + (b+1)%3 + + tri.legalize(a) + return tri.legalize(br) + } + + return ar +} + +func (tri *triangulator) convexHull() []Vertex { + var result []Vertex + e := tri.hull + for e != nil { + result = append(result, tri.points[e.i]) + e = e.prev + if e == tri.hull { + break + } + } + return result +}