mirror of https://github.com/tidwall/tile38.git
258 lines
5.5 KiB
Go
258 lines
5.5 KiB
Go
package geometry
|
|
|
|
import (
|
|
"encoding/binary"
|
|
)
|
|
|
|
const qMaxItems = 32
|
|
const qMaxDepth = 16
|
|
|
|
type qNode struct {
|
|
split bool
|
|
items []uint32
|
|
quads [4]*qNode
|
|
}
|
|
|
|
func (n *qNode) insert(series *baseSeries, bounds, rect Rect, item, depth int) {
|
|
if depth == qMaxDepth {
|
|
// limit depth and insert now
|
|
n.items = append(n.items, uint32(item))
|
|
} else if n.split {
|
|
// qnode is split so try to insert into a quad
|
|
q := n.chooseQuad(bounds, rect)
|
|
if q == -1 {
|
|
// insert into overflow
|
|
n.items = append(n.items, uint32(item))
|
|
} else {
|
|
// insert into quad
|
|
qbounds := quadBounds(bounds, q)
|
|
if n.quads[q] == nil {
|
|
n.quads[q] = new(qNode)
|
|
}
|
|
n.quads[q].insert(series, qbounds, rect, item, depth+1)
|
|
}
|
|
} else if len(n.items) == qMaxItems {
|
|
// split qnode, keep current items in place
|
|
var nitems []uint32
|
|
for i := 0; i < len(n.items); i++ {
|
|
iitem := n.items[i]
|
|
irect := series.SegmentAt(int(iitem)).Rect()
|
|
q := n.chooseQuad(bounds, irect)
|
|
if q == -1 {
|
|
nitems = append(nitems, iitem)
|
|
} else {
|
|
qbounds := quadBounds(bounds, q)
|
|
if n.quads[q] == nil {
|
|
n.quads[q] = new(qNode)
|
|
}
|
|
n.quads[q].insert(series, qbounds, irect, int(iitem), depth+1)
|
|
}
|
|
}
|
|
n.items = nitems
|
|
n.split = true
|
|
n.insert(series, bounds, rect, item, depth)
|
|
} else {
|
|
n.items = append(n.items, uint32(item))
|
|
}
|
|
}
|
|
|
|
func (n *qNode) chooseQuad(bounds, rect Rect) int {
|
|
mid := Point{
|
|
X: (bounds.Min.X + bounds.Max.X) / 2,
|
|
Y: (bounds.Min.Y + bounds.Max.Y) / 2,
|
|
}
|
|
if rect.Max.X < mid.X {
|
|
if rect.Max.Y < mid.Y {
|
|
return 2
|
|
}
|
|
if rect.Min.Y < mid.Y {
|
|
return -1
|
|
}
|
|
return 0
|
|
}
|
|
if rect.Min.X < mid.X {
|
|
return -1
|
|
}
|
|
if rect.Max.Y < mid.Y {
|
|
return 3
|
|
}
|
|
if rect.Min.Y < mid.Y {
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func quadBounds(bounds Rect, q int) (qbounds Rect) {
|
|
switch q {
|
|
case 0:
|
|
qbounds.Min.X = bounds.Min.X
|
|
qbounds.Min.Y = (bounds.Min.Y + bounds.Max.Y) / 2
|
|
qbounds.Max.X = (bounds.Min.X + bounds.Max.X) / 2
|
|
qbounds.Max.Y = bounds.Max.Y
|
|
case 1:
|
|
qbounds.Min.X = (bounds.Min.X + bounds.Max.X) / 2
|
|
qbounds.Min.Y = (bounds.Min.Y + bounds.Max.Y) / 2
|
|
qbounds.Max.X = bounds.Max.X
|
|
qbounds.Max.Y = bounds.Max.Y
|
|
case 2:
|
|
qbounds.Min.X = bounds.Min.X
|
|
qbounds.Min.Y = bounds.Min.Y
|
|
qbounds.Max.X = (bounds.Min.X + bounds.Max.X) / 2
|
|
qbounds.Max.Y = (bounds.Min.Y + bounds.Max.Y) / 2
|
|
case 3:
|
|
qbounds.Min.X = (bounds.Min.X + bounds.Max.X) / 2
|
|
qbounds.Min.Y = bounds.Min.Y
|
|
qbounds.Max.X = bounds.Max.X
|
|
qbounds.Max.Y = (bounds.Min.Y + bounds.Max.Y) / 2
|
|
}
|
|
return
|
|
}
|
|
|
|
func (n *qNode) search(
|
|
series *baseSeries,
|
|
bounds, rect Rect,
|
|
iter func(seg Segment, item int) bool,
|
|
) bool {
|
|
for _, item := range n.items {
|
|
seg := series.SegmentAt(int(item))
|
|
irect := seg.Rect()
|
|
if irect.IntersectsRect(rect) {
|
|
if !iter(seg, int(item)) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
if n.split {
|
|
for q := 0; q < 4; q++ {
|
|
if n.quads[q] != nil {
|
|
qbounds := quadBounds(bounds, q)
|
|
if qbounds.IntersectsRect(rect) {
|
|
if !n.quads[q].search(series, qbounds, rect, iter) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
func numBytes(n uint32) byte {
|
|
if n <= 0xFF {
|
|
return 1
|
|
}
|
|
if n <= 0xFFFF {
|
|
return 2
|
|
}
|
|
return 4
|
|
}
|
|
|
|
func appendNum(dst []byte, num uint32, ibytes byte) []byte {
|
|
switch ibytes {
|
|
case 1:
|
|
dst = append(dst, byte(num))
|
|
case 2:
|
|
dst = append(dst, 0, 0)
|
|
binary.LittleEndian.PutUint16(dst[len(dst)-2:], uint16(num))
|
|
default:
|
|
dst = append(dst, 0, 0, 0, 0)
|
|
binary.LittleEndian.PutUint32(dst[len(dst)-4:], uint32(num))
|
|
}
|
|
return dst
|
|
}
|
|
|
|
func readNum(data []byte, ibytes byte) uint32 {
|
|
switch ibytes {
|
|
case 1:
|
|
return uint32(data[0])
|
|
case 2:
|
|
return uint32(binary.LittleEndian.Uint16(data))
|
|
default:
|
|
return binary.LittleEndian.Uint32(data)
|
|
}
|
|
}
|
|
func (n *qNode) compress(dst []byte, bounds Rect) []byte {
|
|
ibytes := numBytes(uint32(len(n.items)))
|
|
for i := 0; i < len(n.items); i++ {
|
|
ibytes2 := numBytes(n.items[i])
|
|
if ibytes2 > ibytes {
|
|
ibytes = ibytes2
|
|
}
|
|
}
|
|
dst = append(dst, ibytes)
|
|
dst = appendNum(dst, uint32(len(n.items)), ibytes)
|
|
for i := 0; i < len(n.items); i++ {
|
|
dst = appendNum(dst, n.items[i], ibytes)
|
|
}
|
|
if !n.split {
|
|
dst = append(dst, 0)
|
|
return dst
|
|
}
|
|
// store the quads
|
|
dst = append(dst, 1)
|
|
// first make the address space
|
|
var mark [4]int
|
|
for q := 0; q < 4; q++ {
|
|
if n.quads[q] == nil {
|
|
// no quad, no address
|
|
dst = append(dst, 0)
|
|
} else {
|
|
// yes quad, plus addres
|
|
dst = append(dst, 1)
|
|
mark[q] = len(dst)
|
|
dst = append(dst, 0, 0, 0, 0)
|
|
}
|
|
}
|
|
// next add each quad
|
|
for q := 0; q < 4; q++ {
|
|
if n.quads[q] != nil {
|
|
binary.LittleEndian.PutUint32(dst[mark[q]:], uint32(len(dst)))
|
|
dst = n.quads[q].compress(dst, quadBounds(bounds, q))
|
|
}
|
|
}
|
|
return dst
|
|
}
|
|
|
|
func qCompressSearch(
|
|
data []byte,
|
|
addr int,
|
|
series *baseSeries,
|
|
bounds, rect Rect,
|
|
iter func(seg Segment, item int) bool,
|
|
) bool {
|
|
ibytes := data[addr]
|
|
addr++
|
|
nItems := int(readNum(data[addr:], ibytes))
|
|
addr += int(ibytes)
|
|
for i := 0; i < nItems; i++ {
|
|
item := int(readNum(data[addr:], ibytes))
|
|
addr += int(ibytes)
|
|
seg := series.SegmentAt(int(item))
|
|
irect := seg.Rect()
|
|
if irect.IntersectsRect(rect) {
|
|
if !iter(seg, int(item)) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
split := data[addr] == 1
|
|
addr++
|
|
if split {
|
|
for q := 0; q < 4; q++ {
|
|
use := data[addr] == 1
|
|
addr++
|
|
if !use {
|
|
continue
|
|
}
|
|
naddr := int(binary.LittleEndian.Uint32(data[addr:]))
|
|
addr += 4
|
|
qbounds := quadBounds(bounds, q)
|
|
if qbounds.IntersectsRect(rect) {
|
|
if !qCompressSearch(data, naddr, series, qbounds, rect, iter) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|