|
@@ -0,0 +1,557 @@
|
|
|
|
+package main
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "fmt"
|
|
|
|
+ "image"
|
|
|
|
+ "image/color"
|
|
|
|
+ "log"
|
|
|
|
+ "math/rand"
|
|
|
|
+ "os"
|
|
|
|
+ "strings"
|
|
|
|
+ "time"
|
|
|
|
+
|
|
|
|
+ "gioui.org/app"
|
|
|
|
+ "gioui.org/f32"
|
|
|
|
+ "gioui.org/font/gofont"
|
|
|
|
+ "gioui.org/io/key"
|
|
|
|
+ "gioui.org/layout"
|
|
|
|
+ "gioui.org/op"
|
|
|
|
+ "gioui.org/op/clip"
|
|
|
|
+ "gioui.org/op/paint"
|
|
|
|
+ "gioui.org/text"
|
|
|
|
+ "gioui.org/unit"
|
|
|
|
+ "gioui.org/widget/material"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+type C = layout.Context
|
|
|
|
+type D = layout.Dimensions
|
|
|
|
+
|
|
|
|
+var darkPalette = material.Palette{
|
|
|
|
+ Bg: color.NRGBA{R: 0x28, G: 0x28, B: 0x28, A: 0xff},
|
|
|
|
+ Fg: color.NRGBA{R: 0xeb, G: 0xdb, B: 0xb2, A: 0xff},
|
|
|
|
+ ContrastBg: color.NRGBA{R: 0x48, G: 0x85, B: 0x88, A: 0xff},
|
|
|
|
+ ContrastFg: color.NRGBA{R: 0xeb, G: 0xdb, B: 0xb2, A: 0xff},
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func main() {
|
|
|
|
+ go func() {
|
|
|
|
+ window := new(app.Window)
|
|
|
|
+ err := run(window)
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Fatal(err)
|
|
|
|
+ }
|
|
|
|
+ os.Exit(0)
|
|
|
|
+ }()
|
|
|
|
+
|
|
|
|
+ app.Main()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func run(window *app.Window) error {
|
|
|
|
+ theme := material.NewTheme()
|
|
|
|
+ theme.Palette = darkPalette
|
|
|
|
+ theme.Shaper =
|
|
|
|
+ text.NewShaper(text.WithCollection(gofont.Collection()))
|
|
|
|
+
|
|
|
|
+ g := Graph{}
|
|
|
|
+ g.random = rand.New(rand.NewSource(0))
|
|
|
|
+ g.offsets = g._offsets[:]
|
|
|
|
+ g.nodes = g._nodes[:0]
|
|
|
|
+ g.allocs = g._allocs[:]
|
|
|
|
+ g.edges = g._edges[:0]
|
|
|
|
+
|
|
|
|
+ //generateRandomGraph(&g)
|
|
|
|
+
|
|
|
|
+ title := material.Body1(theme, "")
|
|
|
|
+ title.Color = theme.Fg
|
|
|
|
+ title.Alignment = text.Start
|
|
|
|
+
|
|
|
|
+ simPeriod := 50 * time.Millisecond
|
|
|
|
+ lastStep := time.Now()
|
|
|
|
+ bumpShift := 1
|
|
|
|
+ lastSecond := time.Now()
|
|
|
|
+ frameCnt := 0
|
|
|
|
+ stepCnt := 0
|
|
|
|
+ fps, sps := 0, 0
|
|
|
|
+ lastFinish := time.Now()
|
|
|
|
+ var (
|
|
|
|
+ stepOut strings.Builder
|
|
|
|
+ statusText strings.Builder
|
|
|
|
+ ops op.Ops
|
|
|
|
+ screensaverMode bool
|
|
|
|
+ finished bool
|
|
|
|
+ )
|
|
|
|
+ for {
|
|
|
|
+ switch e := window.Event().(type) {
|
|
|
|
+ case app.DestroyEvent:
|
|
|
|
+ return e.Err
|
|
|
|
+
|
|
|
|
+ case app.FrameEvent:
|
|
|
|
+ gtx := app.NewContext(&ops, e)
|
|
|
|
+
|
|
|
|
+ // Handle input
|
|
|
|
+ var (
|
|
|
|
+ resetRequested,
|
|
|
|
+ bumpRequested bool
|
|
|
|
+ )
|
|
|
|
+ for {
|
|
|
|
+ event, ok := e.Source.Event(
|
|
|
|
+ key.Filter{Name: "B"},
|
|
|
|
+ key.Filter{Name: "R"},
|
|
|
|
+ key.Filter{Name: "S", Required: key.ModShift},
|
|
|
|
+ key.Filter{Name: "<", Required: key.ModShift},
|
|
|
|
+ key.Filter{Name: ">", Required: key.ModShift},
|
|
|
|
+ )
|
|
|
|
+ if !ok {
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ switch evt := event.(type) {
|
|
|
|
+ case key.Event:
|
|
|
|
+ switch evt.Name {
|
|
|
|
+ case "B":
|
|
|
|
+ if evt.State == key.Press {
|
|
|
|
+ bumpRequested = true
|
|
|
|
+ }
|
|
|
|
+ case "R":
|
|
|
|
+ if evt.State == key.Press {
|
|
|
|
+ resetRequested = true
|
|
|
|
+ }
|
|
|
|
+ case "S":
|
|
|
|
+ if evt.State == key.Press {
|
|
|
|
+ screensaverMode = true
|
|
|
|
+ }
|
|
|
|
+ case "<":
|
|
|
|
+ if evt.State == key.Press {
|
|
|
|
+ simPeriod += 50 * time.Millisecond
|
|
|
|
+ simPeriod = min(simPeriod, 10*time.Second)
|
|
|
|
+ }
|
|
|
|
+ case ">":
|
|
|
|
+ if evt.State == key.Press {
|
|
|
|
+ simPeriod -= 50 * time.Millisecond
|
|
|
|
+ simPeriod = max(simPeriod, 0)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if resetRequested ||
|
|
|
|
+ (screensaverMode &&
|
|
|
|
+ time.Since(lastFinish) >= 10*time.Second) {
|
|
|
|
+ (&g).Reset()
|
|
|
|
+ lastFinish = time.Now()
|
|
|
|
+ finished = false
|
|
|
|
+ }
|
|
|
|
+ paint.Fill(&ops, theme.Bg)
|
|
|
|
+
|
|
|
|
+ //margin := layout.UniformInset(unit.Dp(8))
|
|
|
|
+ layout.Flex{
|
|
|
|
+ Axis: layout.Vertical,
|
|
|
|
+ Alignment: layout.Start,
|
|
|
|
+ Spacing: layout.SpaceEnd,
|
|
|
|
+ }.Layout(gtx,
|
|
|
|
+ layout.Rigid(func(gtx C) D {
|
|
|
|
+ return (&g).Layout(gtx, theme)
|
|
|
|
+ }),
|
|
|
|
+ layout.Rigid(func(gtx C) D {
|
|
|
|
+ return title.Layout(gtx)
|
|
|
|
+ }),
|
|
|
|
+ layout.Rigid(func(gtx C) D {
|
|
|
|
+ return (&g).LayoutDist(gtx, theme)
|
|
|
|
+ }),
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ if time.Since(lastSecond) >= time.Second {
|
|
|
|
+ fps, sps = frameCnt, stepCnt
|
|
|
|
+ frameCnt, stepCnt = 0, 0
|
|
|
|
+ lastSecond = time.Now()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ statusText.Reset()
|
|
|
|
+ statusText.WriteString(fmt.Sprintf("simulation rate: %s per step\n", simPeriod))
|
|
|
|
+ statusText.WriteString(fmt.Sprintf("fps=%d; sps=%d\n", fps, sps))
|
|
|
|
+
|
|
|
|
+ if time.Since(lastStep) >= simPeriod {
|
|
|
|
+ if int(g.order) == cap(g.nodes) && bumpRequested {
|
|
|
|
+ bump(&g, bumpShift)
|
|
|
|
+ bumpShift++
|
|
|
|
+ }
|
|
|
|
+ stepOut.Reset()
|
|
|
|
+ stepOut.WriteString((&g).Step())
|
|
|
|
+ //statusText.WriteString((&g).Semilattice())
|
|
|
|
+ lastStep = time.Now()
|
|
|
|
+
|
|
|
|
+ addRandomNode(&g)
|
|
|
|
+ stepCnt++
|
|
|
|
+ }
|
|
|
|
+ statusText.WriteString(stepOut.String())
|
|
|
|
+ title.Text = statusText.String()
|
|
|
|
+
|
|
|
|
+ if int(g.order) == cap(g.nodes) && !finished {
|
|
|
|
+ finished = true
|
|
|
|
+ }
|
|
|
|
+ e.Source.Execute(op.InvalidateCmd{})
|
|
|
|
+
|
|
|
|
+ e.Frame(gtx.Ops)
|
|
|
|
+ frameCnt++
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func bump(g *Graph, shift int) int {
|
|
|
|
+ root := -1
|
|
|
|
+ for n := len(g.nodes) - 1; n >= 0; n-- {
|
|
|
|
+ if g.nodes[n] == 1 {
|
|
|
|
+ root = n
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if root < 0 {
|
|
|
|
+ return root
|
|
|
|
+ }
|
|
|
|
+ g.nodes[root] <<= shift
|
|
|
|
+
|
|
|
|
+ nodes := make([]NodeId, 1, g.order)
|
|
|
|
+ nextNodes := make(map[NodeId]struct{}, g.order)
|
|
|
|
+ nodes[0] = NodeId(root)
|
|
|
|
+
|
|
|
|
+ for len(nodes) > 0 {
|
|
|
|
+ for _, n := range nodes {
|
|
|
|
+ for _, e := range g.edges[g.offsets[n]:g.offsets[n+1]] {
|
|
|
|
+ nextNodes[e] = struct{}{}
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ nodes = nodes[:len(nextNodes)]
|
|
|
|
+ i := 0
|
|
|
|
+ for n := range nextNodes {
|
|
|
|
+ delete(nextNodes, n)
|
|
|
|
+ nodes[i] = n
|
|
|
|
+ i++
|
|
|
|
+ if log2(g.nodes[n])+uint32(shift) >= 31 {
|
|
|
|
+ // Out of address space
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ g.nodes[n] <<= shift
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return root
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func addRandomNode(g *Graph) (order uint32) {
|
|
|
|
+ order = g.order
|
|
|
|
+ if g.order == uint32(cap(g.nodes)) ||
|
|
|
|
+ g.size == uint32(cap(g.edges)) {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ threshold := 0.02 * float64(g.order)
|
|
|
|
+
|
|
|
|
+ if g.random.ExpFloat64() <= threshold {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ g.edges = g.edges[:cap(g.edges)]
|
|
|
|
+ g.nodes = g.nodes[:cap(g.nodes)]
|
|
|
|
+
|
|
|
|
+ i := int(g.order)
|
|
|
|
+ g.nodes[i] = 1
|
|
|
|
+ g.offsets[i] = EdgeId(g.size)
|
|
|
|
+
|
|
|
|
+ threshold = float64(2.0)
|
|
|
|
+
|
|
|
|
+ for j := 1; i+j < len(g.nodes); j++ {
|
|
|
|
+ g.edges[g.size] = NodeId(i + j)
|
|
|
|
+
|
|
|
|
+ if g.random.ExpFloat64() <= threshold {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ g.size++
|
|
|
|
+ }
|
|
|
|
+ g.order++
|
|
|
|
+ order = g.order
|
|
|
|
+ g.offsets[g.order] = EdgeId(g.size)
|
|
|
|
+ g.nodes = g.nodes[:g.order]
|
|
|
|
+ g.edges = g.edges[:g.size]
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func generateRandomGraph(g *Graph) {
|
|
|
|
+ k := EdgeId(0) // Graph size
|
|
|
|
+
|
|
|
|
+ for n := 0; n < len(g.nodes); n++ {
|
|
|
|
+ g.nodes[n] = 1
|
|
|
|
+ g.offsets[n] = k
|
|
|
|
+
|
|
|
|
+ weight := float64(0)
|
|
|
|
+ for i := 1; n+i < len(g.nodes); i++ {
|
|
|
|
+ g.edges[k] = NodeId(n + i)
|
|
|
|
+
|
|
|
|
+ threshold :=
|
|
|
|
+ 1.4 + weight*float64(n)/float64(len(g.nodes))
|
|
|
|
+
|
|
|
|
+ if g.random.ExpFloat64() <= threshold {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ weight++
|
|
|
|
+ k++
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ g.offsets[len(g.nodes)] = k
|
|
|
|
+ g.edges = g.edges[:k]
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type NodeId uint32
|
|
|
|
+type EdgeId uint32
|
|
|
|
+
|
|
|
|
+const GraphOrder = 64
|
|
|
|
+
|
|
|
|
+type Graph struct {
|
|
|
|
+ _offsets [GraphOrder + 1]EdgeId
|
|
|
|
+ _nodes [GraphOrder]uint32
|
|
|
|
+ _allocs [GraphOrder]uint32
|
|
|
|
+ _edges [(GraphOrder * (GraphOrder - 1)) >> 2]NodeId
|
|
|
|
+ random *rand.Rand
|
|
|
|
+ offsets []EdgeId
|
|
|
|
+ nodes []uint32
|
|
|
|
+ allocs []uint32 // Address allocation state, per node
|
|
|
|
+ edges []NodeId
|
|
|
|
+ size uint32 // Number of edges added to graph
|
|
|
|
+ order uint32 // Number of nodes added to graph
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (g *Graph) Reset() {
|
|
|
|
+ for o := range g.offsets {
|
|
|
|
+ g.offsets[o] = 0
|
|
|
|
+ }
|
|
|
|
+ for n := range g.nodes {
|
|
|
|
+ g.nodes[n] = 0
|
|
|
|
+ }
|
|
|
|
+ for a := range g.allocs {
|
|
|
|
+ g.allocs[a] = 0
|
|
|
|
+ }
|
|
|
|
+ for e := range g.edges {
|
|
|
|
+ g.edges[e] = 0
|
|
|
|
+ }
|
|
|
|
+ g.nodes = g.nodes[:0]
|
|
|
|
+ g.edges = g.edges[:0]
|
|
|
|
+ g.order, g.size = 0, 0
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (g *Graph) Semilattice() string {
|
|
|
|
+ var b strings.Builder
|
|
|
|
+ for n := 0; n < len(g.nodes); n++ {
|
|
|
|
+ b.WriteString(fmt.Sprintf("nodeAddr=%d neighbors=", g.nodes[n]))
|
|
|
|
+
|
|
|
|
+ for _, e := range g.edges[g.offsets[n]:g.offsets[n+1]] {
|
|
|
|
+ if uint32(e) >= g.order {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ b.WriteString(fmt.Sprintf("%d, ", g.nodes[e]))
|
|
|
|
+ }
|
|
|
|
+ b.WriteString("\n")
|
|
|
|
+ }
|
|
|
|
+ return b.String()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (g *Graph) Step() string {
|
|
|
|
+ for n := range g.nodes {
|
|
|
|
+ if g.allocs[n] == 0 {
|
|
|
|
+ g.allocs[n] = 1 // Seed allocator
|
|
|
|
+ }
|
|
|
|
+ shift := log2(g.nodes[n]) + 1
|
|
|
|
+
|
|
|
|
+ // Assign addresses to neighbors
|
|
|
|
+ for _, e := range g.edges[g.offsets[n]:g.offsets[n+1]] {
|
|
|
|
+ if uint32(e) >= g.order ||
|
|
|
|
+ g.nodes[n] < g.nodes[e] {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ if log2(g.allocs[n])+shift >= 30 {
|
|
|
|
+ // Out of address space
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ nextAddr := (g.allocs[n] << shift) | g.nodes[n]
|
|
|
|
+ g.nodes[e] = nextAddr
|
|
|
|
+ g.allocs[n]++
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return fmt.Sprintf("nodes=%v\n", g.nodes)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (g *Graph) Layout(
|
|
|
|
+ gtx layout.Context,
|
|
|
|
+ th *material.Theme,
|
|
|
|
+) D {
|
|
|
|
+ squareMax := min(
|
|
|
|
+ gtx.Constraints.Max.X,
|
|
|
|
+ gtx.Constraints.Max.Y,
|
|
|
|
+ )
|
|
|
|
+ convFactor := float32(squareMax) / 65535.0
|
|
|
|
+ circle := clip.Ellipse{Max: image.Pt(16, 16)}
|
|
|
|
+
|
|
|
|
+ defer op.Offset(image.Pt(10, 10)).Push(gtx.Ops).Pop()
|
|
|
|
+
|
|
|
|
+ var path clip.Path
|
|
|
|
+ for n := 0; n < len(g.nodes); n++ {
|
|
|
|
+ if g.nodes[n] == 1 { // Ignore root and orphaned nodes
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ x1, y1 := idToPt(g.nodes[n], convFactor)
|
|
|
|
+
|
|
|
|
+ for e := range g.edges[g.offsets[n]:g.offsets[n+1]] {
|
|
|
|
+ if uint32(e) >= g.order {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ color := th.Fg
|
|
|
|
+ if distance(g.nodes[n], g.nodes[e]) > 4 {
|
|
|
|
+ color = th.ContrastBg
|
|
|
|
+ }
|
|
|
|
+ x2, y2 := idToPt(g.nodes[e], convFactor)
|
|
|
|
+
|
|
|
|
+ path.Begin(gtx.Ops)
|
|
|
|
+ path.MoveTo(f32.Pt(float32(x1), float32(y1)))
|
|
|
|
+ path.LineTo(f32.Pt(float32(x2), float32(y2)))
|
|
|
|
+ paint.FillShape(gtx.Ops, color,
|
|
|
|
+ clip.Stroke{
|
|
|
|
+ Path: path.End(),
|
|
|
|
+ Width: float32(unit.Dp(1)),
|
|
|
|
+ }.Op(),
|
|
|
|
+ )
|
|
|
|
+ }
|
|
|
|
+ shift := int(log2(g.nodes[n]) >> 3)
|
|
|
|
+ scaled := circle
|
|
|
|
+ scaled.Max.X = scaled.Max.X >> shift
|
|
|
|
+ scaled.Max.Y = scaled.Max.Y >> shift
|
|
|
|
+ halfW, halfH := scaled.Max.X>>1, scaled.Max.Y>>1
|
|
|
|
+
|
|
|
|
+ pos := image.Pt(gtx.Dp(x1)-halfW, gtx.Dp(y1)-halfH)
|
|
|
|
+
|
|
|
|
+ stack := op.Offset(pos).Push(gtx.Ops)
|
|
|
|
+ paint.FillShape(gtx.Ops, th.Fg, scaled.Op(gtx.Ops))
|
|
|
|
+ stack.Pop()
|
|
|
|
+ }
|
|
|
|
+ return layout.Dimensions{Size: image.Pt(squareMax, squareMax)}
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (g *Graph) LayoutDist(
|
|
|
|
+ gtx layout.Context,
|
|
|
|
+ th *material.Theme,
|
|
|
|
+) D {
|
|
|
|
+ dims := f32.Pt(400, 100)
|
|
|
|
+ width := gtx.Dp(unit.Dp(dims.X))
|
|
|
|
+ height := gtx.Dp(unit.Dp(dims.Y))
|
|
|
|
+
|
|
|
|
+ defer op.Affine( // Flip y-axis and scale
|
|
|
|
+ f32.Affine2D{}.
|
|
|
|
+ Scale(f32.Pt(dims.X/2, dims.Y/2), f32.Pt(1.0, -1.0)),
|
|
|
|
+ ).Push(gtx.Ops).Pop()
|
|
|
|
+
|
|
|
|
+ barWidth := 0
|
|
|
|
+ if g.order > 0 {
|
|
|
|
+ pxPerBucket := float32(width) / float32(g.order)
|
|
|
|
+ barWidth = gtx.Dp(unit.Dp(pxPerBucket))
|
|
|
|
+ }
|
|
|
|
+ color := color.NRGBA{R: 0xaa, G: 0, B: 0xaa, A: 0xff}
|
|
|
|
+
|
|
|
|
+ maxOutDegree := 0
|
|
|
|
+ for n := range g.nodes {
|
|
|
|
+ d := g.offsets[n+1] - g.offsets[n]
|
|
|
|
+ maxOutDegree = max(maxOutDegree, int(d))
|
|
|
|
+ }
|
|
|
|
+ convFactor := float64(height) / float64(maxOutDegree)
|
|
|
|
+
|
|
|
|
+ for n := range g.nodes {
|
|
|
|
+ cnt := g.offsets[n+1] - g.offsets[n]
|
|
|
|
+ y := gtx.Dp(unit.Dp(float64(cnt) * convFactor))
|
|
|
|
+ stack := clip.Rect{
|
|
|
|
+ Min: image.Pt(n*barWidth, 0),
|
|
|
|
+ Max: image.Pt((n+1)*barWidth, y),
|
|
|
|
+ }.Push(gtx.Ops)
|
|
|
|
+
|
|
|
|
+ paint.ColorOp{Color: color}.Add(gtx.Ops)
|
|
|
|
+ paint.PaintOp{}.Add(gtx.Ops)
|
|
|
|
+ stack.Pop()
|
|
|
|
+ }
|
|
|
|
+ return layout.Dimensions{Size: image.Pt(width, height)}
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func distance(a, b uint32) uint32 {
|
|
|
|
+ var s, e uint32 = 1, 2
|
|
|
|
+ for s < 32 && a&(e-1) == b&(e-1) {
|
|
|
|
+ s++
|
|
|
|
+ e <<= 1
|
|
|
|
+ }
|
|
|
|
+ s -= 1
|
|
|
|
+ return ones(a>>s) + ones(b>>s)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func ones(x uint32) uint32 {
|
|
|
|
+ var c uint32
|
|
|
|
+ for c = 0; x != 0; c++ {
|
|
|
|
+ x &= x - 1 // clear the least significant bit set
|
|
|
|
+ }
|
|
|
|
+ return c
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func idToPt(id uint32, convFactor float32) (x, y unit.Dp) {
|
|
|
|
+ x0, y0 := demorton(reverse32(id))
|
|
|
|
+
|
|
|
|
+ x, y = unit.Dp(float32(x0)*convFactor),
|
|
|
|
+ unit.Dp(float32(y0)*convFactor)
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func log2(x uint32) uint32 {
|
|
|
|
+ b := []uint32{0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000}
|
|
|
|
+ S := []uint32{1, 2, 4, 8, 16}
|
|
|
|
+
|
|
|
|
+ var r uint32 = 0
|
|
|
|
+
|
|
|
|
+ for i := 4; i >= 0; i-- {
|
|
|
|
+ if x&b[i] != 0 {
|
|
|
|
+ x >>= S[i]
|
|
|
|
+ r |= S[i]
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return r
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func reverse32(x uint32) uint32 {
|
|
|
|
+ var rev uint32
|
|
|
|
+ var mask uint32 = 0xff
|
|
|
|
+
|
|
|
|
+ for i := 0; i < 4; i++ {
|
|
|
|
+ rshift := 8 * i
|
|
|
|
+ lshift := 8 * (3 - i)
|
|
|
|
+ rev |= uint32(reverse((x&mask)>>rshift)) << lshift
|
|
|
|
+ mask <<= 8
|
|
|
|
+ }
|
|
|
|
+ return rev
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func reverse(b uint32) byte {
|
|
|
|
+ b = (b * 0x0802 & 0x22110) | (b * 0x8020 & 0x88440)
|
|
|
|
+ b *= 0x10101
|
|
|
|
+ b >>= 16
|
|
|
|
+ return byte(b)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func demorton(z uint32) (x, y uint16) {
|
|
|
|
+ res := (uint64(z) | (uint64(z) << 31)) & 0x5555555555555555
|
|
|
|
+ res = (res | (res >> 1)) & 0x3333333333333333
|
|
|
|
+ res = (res | (res >> 2)) & 0x0f0f0f0f0f0f0f0f
|
|
|
|
+ res = (res | (res >> 4)) & 0x00ff00ff00ff00ff
|
|
|
|
+ res = res | (res >> 8)
|
|
|
|
+ x = uint16(res)
|
|
|
|
+ y = uint16(res >> 32)
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func morton(x, y uint16) uint32 {
|
|
|
|
+ var res uint64
|
|
|
|
+
|
|
|
|
+ res = uint64(x) | (uint64(y) << 32)
|
|
|
|
+ res = (res | (res << 8)) & 0x00ff00ff00ff00ff
|
|
|
|
+ res = (res | (res << 4)) & 0x0f0f0f0f0f0f0f0f
|
|
|
|
+ res = (res | (res << 2)) & 0x3333333333333333
|
|
|
|
+ res = (res | (res << 1)) & 0x5555555555555555
|
|
|
|
+
|
|
|
|
+ return uint32(res | (res >> 31))
|
|
|
|
+}
|