view cmd/srsimplify/main.go @ 3771:b7530ed07561 simplify-sounding-results

Partition recursively. Does not produce good triangles.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Wed, 26 Jun 2019 12:06:28 +0200
parents 71164b817d6e
children 545304d3ff93
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) 2019 by via donau
//   – Österreichische Wasserstraßen-Gesellschaft mbH
// Software engineering by Intevation GmbH
//
// Author(s):
//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>

package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"math"
	"os"
	"strconv"
	"strings"

	"gemma.intevation.de/gemma/pkg/octree"
)

func loadXYZ(r io.Reader) (octree.MultiPointZ, error) {

	scanner := bufio.NewScanner(r)

	points := make(octree.MultiPointZ, 0, 2000000)

	var x, y, z float64
	var err error

	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if len(line) == 0 || strings.HasPrefix(line, "#") {
			continue
		}
		parts := strings.SplitN(line, " ", 3)
		if len(parts) != 3 {
			continue
		}

		if x, err = strconv.ParseFloat(parts[0], 64); err != nil {
			return nil, err
		}
		if y, err = strconv.ParseFloat(parts[1], 64); err != nil {
			return nil, err
		}
		if z, err = strconv.ParseFloat(parts[2], 64); err != nil {
			return nil, err
		}
		points = append(points, octree.Vertex{X: x, Y: y, Z: z})
	}

	return points, nil
}

func storeXYZ(points octree.MultiPointZ, w io.Writer) error {
	out := bufio.NewWriter(w)
	for i := range points {
		fmt.Fprintf(out, "%.5f,%.5f,%.5f\n",
			points[i].X, points[i].Y, points[i].Z)
	}
	return out.Flush()
}

func simplify(points octree.MultiPointZ, tolerance float64) octree.MultiPointZ {

	if len(points) < 2 {
		return points
	}

	if tolerance < 0 {
		tolerance = -tolerance
	}

	min := octree.Vertex{X: math.MaxFloat64, Y: math.MaxFloat64, Z: math.MaxFloat64}
	max := octree.Vertex{X: -math.MaxFloat64, Y: -math.MaxFloat64, Z: -math.MaxFloat64}

	var maxIdx int

	for i, v := range points {
		min.Minimize(v)

		if v.X < min.X {
			min.X = v.X
		}
		if v.X > max.X {
			max.X = v.X
		}
		if v.Y < min.Y {
			min.Y = v.Y
		}
		if v.Y > max.Y {
			max.Y = v.Y
		}
		if v.Z < min.Z {
			min.Z = v.Z
		}
		if v.Z > max.Z {
			max.Z = v.Z
			maxIdx = i
		}

		max.Maximize(v)
	}

	log.Printf("(%.5f, %.5f, %.5f) - (%.5f, %.5f, %.5f)\n",
		min.X, min.Y, min.Z,
		max.X, max.Y, max.Z)

	below := min.Z - 3*tolerance
	xMin := min.X - tolerance
	xMax := max.X + tolerance
	yMin := min.Y - tolerance
	yMax := max.Y + tolerance

	corners := []octree.Vertex{
		{xMin, yMin, below},
		{xMax, yMin, below},
		{xMax, yMax, below},
		{xMin, yMax, below},
	}

	top := points[maxIdx]

	tris := make([]octree.Triangle, len(corners))
	planes := make([]octree.Plane3D, len(corners))

	for i, v1 := range corners {
		v2 := corners[(i+1)%len(corners)]
		tris[i] = octree.Triangle{v1, v2, top}
		planes[i] = tris[i].Plane3D()
	}

	parts := make([][]octree.Vertex, len(tris))

	maxDists := make([]float64, len(planes))
	maxIdxs := make([]int, len(planes))

nextPoint:
	for i, v := range points {
		if i == maxIdx {
			continue
		}

		for j := range tris {
			if tris[j].Contains(v.X, v.Y) {
				if dist := math.Abs(planes[j].Eval(v)); dist > maxDists[j] {
					maxDists[j] = dist
					maxIdxs[j] = len(parts[j])
				}
				parts[j] = append(parts[j], v)
				continue nextPoint
			}
		}
	}

	result := make([]octree.Vertex, 0, len(points))

	var handleTriangle func(*octree.Triangle, float64, int, []octree.Vertex) bool

	handleTriangle = func(
		t *octree.Triangle,
		maxDist float64, maxIdx int,
		points []octree.Vertex,
	) bool {
		if maxDist <= tolerance {
			return false
		}

		if len(points) == 1 {
			result = append(result, points[0])
		}

		var (
			tris     [3]octree.Triangle
			planes   [3]octree.Plane3D
			maxDists [3]float64
			maxIdxs  [3]int
			parts    [3][]octree.Vertex
		)

		top := points[maxIdx]
		for i := 0; i < 3; i++ {
			tris[i] = octree.Triangle{t[i], t[(i+1)%3], top}
			planes[i] = tris[i].Plane3D()
		}

	nextPoint:
		for i, v := range points {
			if i == maxIdx {
				continue
			}

			for j := range tris {
				if tris[j].Contains(v.X, v.Y) {
					if dist := math.Abs(planes[j].Eval(v)); dist > maxDists[j] {
						maxDists[j] = dist
						maxIdxs[j] = len(parts[j])
					}
					parts[j] = append(parts[j], v)
					continue nextPoint
				}
			}
		}

		var found bool
		for i, part := range parts {
			if len(part) > 0 && handleTriangle(&tris[i], maxDists[i], maxIdxs[i], part) {
				found = true
			}
		}

		if found {
			result = append(result, top)
		}

		return found
	}

	var found bool
	for i, part := range parts {
		if len(part) > 0 && handleTriangle(&tris[i], maxDists[i], maxIdxs[i], part) {
			found = true
		}
	}

	if found {
		result = append(result, top)
	}

	return result
}

func main() {

	var tolerance float64

	flag.Float64Var(&tolerance, "t", 0.1, "accepted tolerance (shorthand)")
	flag.Float64Var(&tolerance, "tolerance", 0.1, "accepted tolerance")

	flag.Parse()

	points, err := loadXYZ(os.Stdin)
	if err != nil {
		log.Fatalf("err: %v\n", err)
	}

	points = simplify(points, tolerance)

	if err := storeXYZ(points, os.Stdout); err != nil {
		log.Fatalf("err: %v\n", err)
	}
}