view pkg/mesh/triangulator.go @ 5688:6281c18b109f sr-v2

Finsh serializing v2 meshes.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 12 Feb 2024 02:27:41 +0100
parents f4abfd0ee8ad
children
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 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
}