mirror of https://github.com/tidwall/tile38.git
256 lines
5.5 KiB
Go
256 lines
5.5 KiB
Go
|
package geometry
|
||
|
|
||
|
import "encoding/binary"
|
||
|
|
||
|
const qMaxItems = 16
|
||
|
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
|
||
|
}
|