implemented bsp/build
This commit is contained in:
@@ -1,3 +1,160 @@
|
|||||||
package bsp
|
package bsp
|
||||||
|
|
||||||
//init
|
import (
|
||||||
|
"bspviz/internal/geom"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Leaf struct {
|
||||||
|
Segs []geom.Seg
|
||||||
|
}
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
O, D geom.Vec // Trennlinie: O + t*D
|
||||||
|
Left *Node
|
||||||
|
Right *Node
|
||||||
|
Leaf *Leaf // != nil => Blatt/Subsector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameter für die Heuristik/Build:
|
||||||
|
type Params struct {
|
||||||
|
Alpha float64 // Gewicht Splits
|
||||||
|
Beta float64 // Gewicht Balance
|
||||||
|
Eps float64 // Toleranz (reicht: geom.EPS, aber als Kopie)
|
||||||
|
MaxDepth int // z. B. 32
|
||||||
|
LeafMax int // Max. Segmente pro Leaf (z. B. 12)
|
||||||
|
Cands int // Anzahl Kandidaten (Subsample), z. B. 16
|
||||||
|
Seed int64 // RNG-Seed (0 => Zeit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop entscheidet, ob die Rekursion an dieser Stelle endet und ein Blatt entsteht.
|
||||||
|
func stop(segs []geom.Seg, depth int, p Params) bool {
|
||||||
|
if p.MaxDepth <= 0 {
|
||||||
|
p.MaxDepth = 32
|
||||||
|
}
|
||||||
|
if p.LeafMax <= 0 {
|
||||||
|
p.LeafMax = 12
|
||||||
|
}
|
||||||
|
return depth >= p.MaxDepth || len(segs) <= p.LeafMax
|
||||||
|
}
|
||||||
|
|
||||||
|
// evalCost bewertet eine Kandidaten-Splitebene nach Split-Anzahl, Balance und Gesamtkosten.
|
||||||
|
func evalCost(segs []geom.Seg, O, D geom.Vec, p Params) (splits int, balance int, cost float64) {
|
||||||
|
left, right := 0, 0
|
||||||
|
for _, s := range segs {
|
||||||
|
sa := geom.Side(s.A, O, D)
|
||||||
|
sb := geom.Side(s.B, O, D)
|
||||||
|
if sa >= -p.Eps && sb >= -p.Eps {
|
||||||
|
left++
|
||||||
|
} else if sa <= p.Eps && sb <= p.Eps {
|
||||||
|
right++
|
||||||
|
} else {
|
||||||
|
splits++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Alpha == 0 {
|
||||||
|
p.Alpha = 10
|
||||||
|
}
|
||||||
|
if p.Beta == 0 {
|
||||||
|
p.Beta = 1
|
||||||
|
}
|
||||||
|
balance = int(math.Abs(float64(left - right)))
|
||||||
|
cost = p.Alpha*float64(splits) + p.Beta*float64(balance)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectSplit wählt die heuristisch beste Partitionsebene aus den vorhandenen Segmenten.
|
||||||
|
func selectSplit(segs []geom.Seg, p Params, rng *rand.Rand) (O, D geom.Vec) {
|
||||||
|
n := len(segs)
|
||||||
|
if n == 0 {
|
||||||
|
return geom.Vec{}, geom.Vec{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wie viele Kandidaten?
|
||||||
|
k := p.Cands
|
||||||
|
if k <= 0 {
|
||||||
|
k = 16
|
||||||
|
}
|
||||||
|
if k > n {
|
||||||
|
k = n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple Strategie: gleichmäßig sampeln; bei großem n kleine Randomisierung.
|
||||||
|
step := int(math.Max(1, float64(n)/float64(k)))
|
||||||
|
bestCost := math.MaxFloat64
|
||||||
|
|
||||||
|
for i := 0; i < n; i += step {
|
||||||
|
s := segs[i]
|
||||||
|
Oc := s.A
|
||||||
|
Dc := geom.Sub(s.B, s.A)
|
||||||
|
if geom.Len(Dc) < 1e-9 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, _, c := evalCost(segs, Oc, Dc, p)
|
||||||
|
if c < bestCost {
|
||||||
|
bestCost = c
|
||||||
|
O = Oc
|
||||||
|
D = Dc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionale Randomisierung unter Top-K wäre ein nächster Schritt.
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitAll teilt alle Segmente entlang der Geraden O+t*D und filtert Degenerationsfälle heraus.
|
||||||
|
func splitAll(segs []geom.Seg, O, D geom.Vec, p Params) (leftSet, rightSet []geom.Seg, numSplits int) {
|
||||||
|
leftSet = make([]geom.Seg, 0, len(segs))
|
||||||
|
rightSet = make([]geom.Seg, 0, len(segs))
|
||||||
|
for _, s := range segs {
|
||||||
|
f, b := geom.SplitSeg(s, O, D)
|
||||||
|
if len(f) > 0 && len(b) > 0 {
|
||||||
|
numSplits++
|
||||||
|
}
|
||||||
|
// optional: sehr kurze Teilstücke verwerfen
|
||||||
|
for _, x := range f {
|
||||||
|
if geom.Len(geom.Sub(x.B, x.A)) >= 5*p.Eps {
|
||||||
|
leftSet = append(leftSet, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, x := range b {
|
||||||
|
if geom.Len(geom.Sub(x.B, x.A)) >= 5*p.Eps {
|
||||||
|
rightSet = append(rightSet, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build erzeugt einen BSP-Baum aus den Segmenten gemäß den übergebenen Parametern.
|
||||||
|
func Build(segs []geom.Seg, p Params) *Node {
|
||||||
|
if p.Eps == 0 {
|
||||||
|
p.Eps = geom.EPS
|
||||||
|
}
|
||||||
|
var seed int64 = p.Seed
|
||||||
|
if seed == 0 {
|
||||||
|
seed = 1
|
||||||
|
} // deterministisch machen, wenn gewünscht
|
||||||
|
rng := rand.New(rand.NewSource(seed))
|
||||||
|
return buildRec(segs, 0, p, rng)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildRec realisiert den rekursiven Aufbau und verteilt Segmente auf linke und rechte Subbäume.
|
||||||
|
func buildRec(segs []geom.Seg, depth int, p Params, rng *rand.Rand) *Node {
|
||||||
|
if stop(segs, depth, p) {
|
||||||
|
return &Node{Leaf: &Leaf{Segs: segs}}
|
||||||
|
}
|
||||||
|
O, D := selectSplit(segs, p, rng)
|
||||||
|
if geom.Len(D) < 1e-9 {
|
||||||
|
// Fallback: notgedrungen Leaf
|
||||||
|
return &Node{Leaf: &Leaf{Segs: segs}}
|
||||||
|
}
|
||||||
|
leftSet, rightSet, _ := splitAll(segs, O, D, p)
|
||||||
|
return &Node{
|
||||||
|
O: O, D: D,
|
||||||
|
Left: buildRec(leftSet, depth+1, p, rng),
|
||||||
|
Right: buildRec(rightSet, depth+1, p, rng),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user