implemented bsp/build
This commit is contained in:
@@ -1,3 +1,160 @@
|
||||
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