view pkg/octree/tree.go @ 2504:e0a7571ac13a octree-diff

Eliminate bad triangles. Not really the right solution.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 04 Mar 2019 18:15:38 +0100
parents 5c3e63cfd50d
children 1ec4c5633eb6
line wrap: on
line source

// This is Free Software under GNU Affero General Public License v >= 3.0
// without warranty, see README.md and license for details.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// License-Filename: LICENSES/AGPL-3.0.txt
//
// Copyright (C) 2018 by via donau
//   – Österreichische Wasserstraßen-Gesellschaft mbH
// Software engineering by Intevation GmbH
//
// Author(s):
//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>

package octree

import (
	"fmt"
	"log"
	"math"
	"os"
)

// Tree is an Octree holding triangles.
type Tree struct {
	// EPSG is the projection.
	EPSG uint32

	vertices  []Vertex
	triangles [][]int32
	index     []int32

	// Min is the lower left corner of the bbox.
	Min Vertex
	// Max is the upper right corner of the bbox.
	Max Vertex
}

type boxFrame struct {
	pos int32
	Box2D
}

func (ot *Tree) Vertices() []Vertex {
	return ot.vertices
}

var scale = [4][4]float64{
	{0.0, 0.0, 0.5, 0.5},
	{0.5, 0.0, 1.0, 0.5},
	{0.0, 0.5, 0.5, 1.0},
	{0.5, 0.5, 1.0, 1.0},
}

func (ot *Tree) Dump() error {
	fake, err := os.Create("/tmp/dump.geojson")
	if err != nil {
		return err
	}
	defer fake.Close()

	fmt.Fprintln(fake,
		`{
"crs": {
    "type": "name",
    "properties": {
      "name": "urn:ogc:def:crs:EPSG::32633"
    }
  },

  "type": "FeatureCollection",
  "features": [`)

	first := true

	already := make(map[int32]struct{})

	stack := []int32{1}
	for len(stack) > 0 {
		pos := stack[len(stack)-1]
		stack = stack[:len(stack)-1]

		if pos > 0 { // node
			index := ot.index[pos:]
			if len(index) > 8 {
				index = index[:8]
			}
			for i := range index {
				if index[i] != 0 {
					stack = append(stack, index[i])
				}
			}
		} else {
			pos = -pos - 1
			n := ot.index[pos]
			indices := ot.index[pos+1 : pos+1+n]
			for _, index := range indices {
				if _, found := already[index]; found {
					continue
				}
				already[index] = struct{}{}
				tri := ot.triangles[index]
				t := Triangle{
					ot.vertices[tri[0]],
					ot.vertices[tri[1]],
					ot.vertices[tri[2]],
				}
				if first {
					first = false
				} else {
					fmt.Fprintln(fake, ",")
				}
				fmt.Fprintf(fake,
					`{ "type": "Feature",
  "properties": {
     "id": %d
  },
  "geometry": {
     "type": "Polygon",
     "coordinates": [[
       [ %.6f, %.6f ],
       [ %.6f, %.6f ],
       [ %.6f, %.6f ],
       [ %.6f, %.6f ]]
    ]
  }
}`, index,
					t[0].X, t[0].Y,
					t[1].X, t[1].Y,
					t[2].X, t[2].Y,
					t[0].X, t[0].Y)
			}
		}
	}

	fmt.Fprintln(fake,
		`]
}`)
	return nil
}

func (ot *Tree) eliminate(removed map[int32]IntersectionType) {

	var bad int

	stack := []int32{1}
	for len(stack) > 0 {
		pos := stack[len(stack)-1]
		stack = stack[:len(stack)-1]

		if pos > 0 { // node
			index := ot.index[pos:]
			if len(index) > 8 {
				index = index[:8]
			}
			for i := range index {
				if index[i] != 0 {
					stack = append(stack, index[i])
				}
			}
		} else {
			pos = -pos - 1
			n := ot.index[pos]
			indices := ot.index[pos+1 : pos+1+n]

			for i := len(indices) - 1; i >= 0; i-- {
				index := indices[i]
				if t, found := removed[index]; found && t != IntersectionInside {
					if i < len(indices)-1 {
						copy(indices[i:], indices[i+1:])
					}
					indices[len(indices)-1] = 0
					indices = indices[:len(indices)-1]
					bad++
				}
			}
			ot.index[pos] = int32(len(indices))
		}
	}

	log.Printf("bad triangles: %d\n", bad)
}

func (ot *Tree) Clip(p *Polygon) {

	/*
			fake, _ := os.Create("/tmp/removed.geojson")
			defer fake.Close()

			fmt.Fprintln(fake,
				`{
		"crs": {
		    "type": "name",
		    "properties": {
		      "name": "urn:ogc:def:crs:EPSG::32633"
		    }
		  },

		  "type": "FeatureCollection",
		  "features": [`)

			first := true
	*/

	log.Printf("info: num triangles: %d\n", len(ot.triangles))

	all := Box2D{ot.Min.X, ot.Min.Y, ot.Max.X, ot.Max.Y}

	stack := []boxFrame{{1, all}}

	triChecks := make(map[int32]IntersectionType)

	var nodeTests int
	var nodesClipped int
	var trianglesClipped int
	var nodesAllInside int

frames:
	for len(stack) > 0 {
		top := stack[len(stack)-1]
		stack = stack[:len(stack)-1]

		if top.pos > 0 { // node
			nodeTests++
			switch p.IntersectionBox2D(top.Box2D) {
			case IntersectionInside:
				// all inside so nothing to clip.
				nodesAllInside++
				continue frames
			case IntersectionOutSide:
				// all outside -> clip from tree.
				index := ot.index[top.pos:]
				if len(index) > 8 {
					index = index[:8]
				}
				for i := range index {
					if index[i] != 0 {
						index[i] = 0
						nodesClipped++
					}
				}
				continue frames
			default: // Overlaps
				if index := ot.index[top.pos:]; len(index) > 7 {
				children:
					for i := 0; i < 4; i++ {
						a := index[i]
						b := index[i+4]
						if a == 0 && b == 0 {
							continue
						}
						dx := top.X2 - top.X1
						dy := top.Y2 - top.Y1
						nbox := Box2D{
							dx*scale[i][0] + top.X1,
							dy*scale[i][1] + top.Y1,
							dx*scale[i][2] + top.X1,
							dy*scale[i][3] + top.Y1,
						}
						switch p.IntersectionBox2D(nbox) {
						case IntersectionInside:
							// all inside so nothing to clip.
							nodesAllInside++
							continue children
						case IntersectionOutSide:
							// all are ouside -> clip from tree.
							if a != 0 {
								index[i] = 0
								nodesClipped++
							}
							if b != 0 {
								index[i+4] = 0
								nodesClipped++
							}
							continue children
						default: // Overlaps
							if a != 0 {
								stack = append(stack, boxFrame{a, nbox})
							}
							if b != 0 {
								stack = append(stack, boxFrame{b, nbox})
							}
						}
					}
				}
			}
		} else { // leaf
			pos := -top.pos - 1
			n := ot.index[pos]
			indices := ot.index[pos+1 : pos+1+n]
		tris:
			for i := len(indices) - 1; i >= 0; i-- {
				triIndex := indices[i]
				what, found := triChecks[triIndex]
				if !found {
					tri := ot.triangles[triIndex]
					t := Triangle{
						ot.vertices[tri[0]],
						ot.vertices[tri[1]],
						ot.vertices[tri[2]],
					}
					what = p.IntersectionWithTriangle(&t)
					triChecks[triIndex] = what

					/*

											if what != IntersectionInside {
												if first {
													first = false
												} else {
													fmt.Fprintln(fake, ",")
												}
												fmt.Fprintf(fake,
													`{ "type": "Feature",
						  "properties": {
						    "id": %d
						  },
						  "geometry": {
						     "type": "Polygon",
						     "coordinates": [[
						       [ %.6f, %.6f ],
						       [ %.6f, %.6f ],
						       [ %.6f, %.6f ],
						       [ %.6f, %.6f ]]
						    ]
						  }
						}`, triIndex,
													t[0].X, t[0].Y,
													t[1].X, t[1].Y,
													t[2].X, t[2].Y,
													t[0].X, t[0].Y)
											}
					*/
				}
				switch what {
				case IntersectionInside:
					// triangle inside -> stay.
					continue tris
				default:
					trianglesClipped++
					// outside or not fully covered -> remove.
					if i < len(indices)-1 {
						copy(indices[i:], indices[i+1:])
					}
					indices[len(indices)-1] = 0
					indices = indices[:len(indices)-1]
				}
			}
			ot.index[pos] = int32(len(indices))
		}
	}
	/*
			fmt.Fprintln(fake,
				`]
		}`)
	*/
	log.Printf("info: node tests: %d\n", nodeTests)
	log.Printf("info: nodes clipped: %d\n", nodesClipped)
	log.Printf("info: nodes all inside: %d\n", nodesAllInside)
	log.Printf("info: triangle clipped: %d\n", trianglesClipped)
	log.Printf("info: tested triangles: %d\n", len(triChecks))

	ot.eliminate(triChecks)
}

func (ot *Tree) Value(x, y float64) (float64, bool) {

	// out of bounding box
	if x < ot.Min.X || ot.Max.X < x ||
		y < ot.Min.Y || ot.Max.Y < y {
		return 0, false
	}

	all := Box2D{ot.Min.X, ot.Min.Y, ot.Max.X, ot.Max.Y}

	stack := []boxFrame{{1, all}}

	for len(stack) > 0 {
		top := stack[len(stack)-1]
		stack = stack[:len(stack)-1]

		if top.pos > 0 { // node
			if index := ot.index[top.pos:]; len(index) > 7 {
				for i := 0; i < 4; i++ {
					a := index[i]
					b := index[i+4]
					if a == 0 && b == 0 {
						continue
					}
					dx := top.X2 - top.X1
					dy := top.Y2 - top.Y1
					nbox := Box2D{
						dx*scale[i][0] + top.X1,
						dy*scale[i][1] + top.Y1,
						dx*scale[i][2] + top.X1,
						dy*scale[i][3] + top.Y1,
					}
					if nbox.Contains(x, y) {
						if a != 0 {
							stack = append(stack, boxFrame{a, nbox})
						}
						if b != 0 {
							stack = append(stack, boxFrame{b, nbox})
						}
						break
					}
				}
			}
		} else { // leaf
			pos := -top.pos - 1
			n := ot.index[pos]
			indices := ot.index[pos+1 : pos+1+n]

			for _, idx := range indices {
				tri := ot.triangles[idx]
				t := Triangle{
					ot.vertices[tri[0]],
					ot.vertices[tri[1]],
					ot.vertices[tri[2]],
				}
				if t.Contains(x, y) {
					return t.Plane3D().Z(x, y), true
				}
			}
		}
	}

	return 0, false
}

// Vertical does a vertical cross cut from (x1, y1) to (x2, y2).
func (ot *Tree) Vertical(x1, y1, x2, y2 float64, fn func(*Triangle)) {

	box := Box2D{
		X1: math.Min(x1, x2),
		Y1: math.Min(y1, y2),
		X2: math.Max(x1, x2),
		Y2: math.Max(y1, y2),
	}

	// out of bounding box
	if box.X2 < ot.Min.X || ot.Max.X < box.X1 ||
		box.Y2 < ot.Min.Y || ot.Max.Y < box.Y1 {
		return
	}

	line := NewPlane2D(x1, y1, x2, y2)

	dupes := map[int32]struct{}{}

	all := Box2D{ot.Min.X, ot.Min.Y, ot.Max.X, ot.Max.Y}
	//log.Printf("area: %f\n", (box.x2-box.x1)*(box.y2-box.y1))
	//log.Printf("all: %f\n", (all.x2-all.x1)*(all.y2-all.y1))

	stack := []boxFrame{{1, all}}

	for len(stack) > 0 {
		top := stack[len(stack)-1]
		stack = stack[:len(stack)-1]

		if top.pos > 0 { // node
			if index := ot.index[top.pos:]; len(index) > 7 {
				for i := 0; i < 4; i++ {
					a := index[i]
					b := index[i+4]
					if a == 0 && b == 0 {
						continue
					}
					dx := top.X2 - top.X1
					dy := top.Y2 - top.Y1
					nbox := Box2D{
						dx*scale[i][0] + top.X1,
						dy*scale[i][1] + top.Y1,
						dx*scale[i][2] + top.X1,
						dy*scale[i][3] + top.Y1,
					}
					if nbox.Intersects(box) && nbox.IntersectsPlane(line) {
						if a != 0 {
							stack = append(stack, boxFrame{a, nbox})
						}
						if b != 0 {
							stack = append(stack, boxFrame{b, nbox})
						}
					}
				}
			}
		} else { // leaf
			pos := -top.pos - 1
			n := ot.index[pos]
			indices := ot.index[pos+1 : pos+1+n]

			for _, idx := range indices {
				if _, found := dupes[idx]; found {
					continue
				}
				tri := ot.triangles[idx]
				t := Triangle{
					ot.vertices[tri[0]],
					ot.vertices[tri[1]],
					ot.vertices[tri[2]],
				}

				v0 := line.Eval(t[0].X, t[0].Y)
				v1 := line.Eval(t[1].X, t[1].Y)
				v2 := line.Eval(t[2].X, t[2].Y)

				if onPlane(v0) || onPlane(v1) || onPlane(v2) ||
					sides(sides(sides(0, v0), v1), v2) == 3 {
					fn(&t)
				}
				dupes[idx] = struct{}{}
			}
		}
	}
}

// Horizontal does a horizontal cross cut.
func (ot *Tree) Horizontal(h float64, fn func(*Triangle)) {

	if h < ot.Min.Z || ot.Max.Z < h {
		return
	}

	type frame struct {
		pos int32
		min float64
		max float64
	}

	dupes := map[int32]struct{}{}

	stack := []frame{{1, ot.Min.Z, ot.Max.Z}}

	for len(stack) > 0 {
		top := stack[len(stack)-1]
		stack = stack[:len(stack)-1]

		pos := top.pos
		if pos == 0 {
			continue
		}
		min, max := top.min, top.max

		if pos > 0 { // node
			if mid := (max-min)*0.5 + min; h >= mid {
				pos += 4 // nodes with z-bit set
				min = mid
			} else {
				max = mid
			}
			if pos < int32(len(ot.index)) {
				if index := ot.index[pos:]; len(index) > 3 {
					stack = append(stack,
						frame{index[0], min, max},
						frame{index[1], min, max},
						frame{index[2], min, max},
						frame{index[3], min, max})
				}
			}
		} else { // leaf
			pos = -pos - 1
			n := ot.index[pos]
			//log.Printf("%d %d %d\n", pos, n, len(ot.index))
			indices := ot.index[pos+1 : pos+1+n]

			for _, idx := range indices {
				if _, found := dupes[idx]; found {
					continue
				}
				tri := ot.triangles[idx]
				t := Triangle{
					ot.vertices[tri[0]],
					ot.vertices[tri[1]],
					ot.vertices[tri[2]],
				}

				if !(math.Min(t[0].Z, math.Min(t[1].Z, t[2].Z)) > h ||
					math.Max(t[0].Z, math.Max(t[1].Z, t[2].Z)) < h) {
					dupes[idx] = struct{}{}
					fn(&t)
				}
			}
		}
	}
}