Skip to content

Commit 291c564

Browse files
committed
i3.Cube.Lvl->Level; add Octree.CubeCenter; documentation
1 parent a82e93d commit 291c564

File tree

4 files changed

+67
-50
lines changed

4 files changed

+67
-50
lines changed

i3/icube.go

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,65 @@ package i3
22

33
// Cube implements a tree cube for Octree algorithms.
44
type Cube struct {
5+
// Vec stores the shifted
56
Vec
6-
// Lvl keeps track of the level in the tree.
7-
// - Lvl==1 means the cube is the smallest possible cube.
8-
// - Lvl==0 is an invalid level. May be used as a flag to signal the cube has been discarded or processed and ready for discard.
9-
Lvl int
7+
// Level keeps track of the level in the tree.
8+
// - Level==1 means the cube is the smallest possible cube.
9+
// - Level==0 is an invalid level. May be used as a flag to signal the cube has been discarded or processed and ready for discard.
10+
Level int
1011
}
1112

1213
// IsSmallest returns true if Lvl==1. This means the cube cannot be decomposed further with [Cube.Octree].
13-
func (c Cube) IsSmallest() bool { return c.Lvl == 1 }
14+
func (c Cube) IsSmallest() bool { return c.Level == 1 }
1415

1516
// IsSecondSmallest returns true if Lvl==2. This means the cube can be decomposed once more with [Cube.Octree].
16-
func (c Cube) IsSecondSmallest() bool { return c.Lvl == 2 }
17+
func (c Cube) IsSecondSmallest() bool { return c.Level == 2 }
1718

1819
// DecomposesTo returns the amount of cubes generated from decomposing the cube down to cubes of the argument target level.
19-
func (c Cube) DecomposesTo(targetLvl int) uint64 {
20-
if targetLvl > c.Lvl {
20+
func (c Cube) DecomposesTo(targetLevel int) uint64 {
21+
if targetLevel > c.Level {
2122
panic("invalid targetLvl to icube.decomposesTo")
2223
}
23-
return Pow8(c.Lvl - targetLvl)
24+
return Pow8(c.Level - targetLevel)
2425
}
2526

2627
// Size returns the length of one of the icube's sides.
2728
func (c Cube) Size() (resUnits int) {
28-
return 1 << (c.Lvl - 1)
29+
return 1 << (c.Level - 1)
2930
}
3031

3132
// Supercube returns the ICube3's parent octree ICube3.
3233
func (c Cube) Supercube() Cube {
33-
upLvl := c.Lvl + 1
34-
bitmask := (1 << upLvl) - 1
34+
upLevel := c.Level + 1
35+
bitmask := (1 << upLevel) - 1
3536
return Cube{
36-
Vec: c.Vec.AndnotScalar(bitmask),
37-
Lvl: upLvl,
37+
Vec: c.Vec.AndnotScalar(bitmask),
38+
Level: upLevel,
3839
}
3940
}
4041

4142
// Index returns the indices corresponding to the ICube3 in the root cube.
4243
// By multiplying the resulting indices by the smallest cube size one can obtain the origin of the ICube in space.
4344
func (c Cube) Index() Vec {
44-
return c.Vec.ShiftRight(c.Lvl) // icube indices per level in the octree.
45+
return c.Vec.ShiftRightScalar(c.Level) // icube indices per level in the octree.
4546
}
4647

4748
// Octree returns the 8 sub-cubes of the receiver.
4849
func (c Cube) Octree() [8]Cube {
49-
lvl := c.Lvl - 1
50-
if lvl <= 0 {
50+
level := c.Level - 1
51+
if level <= 0 {
5152
panic("invalid operation: octree for level<=1")
5253
}
53-
s := 1 << lvl
54+
s := 1 << level
5455
return [8]Cube{
55-
{Vec: c.Add(Vec{0, 0, 0}), Lvl: lvl},
56-
{Vec: c.Add(Vec{s, 0, 0}), Lvl: lvl},
57-
{Vec: c.Add(Vec{s, s, 0}), Lvl: lvl},
58-
{Vec: c.Add(Vec{0, s, 0}), Lvl: lvl},
59-
{Vec: c.Add(Vec{0, 0, s}), Lvl: lvl},
60-
{Vec: c.Add(Vec{s, 0, s}), Lvl: lvl},
61-
{Vec: c.Add(Vec{s, s, s}), Lvl: lvl},
62-
{Vec: c.Add(Vec{0, s, s}), Lvl: lvl},
56+
{Vec: c.Add(Vec{0, 0, 0}), Level: level},
57+
{Vec: c.Add(Vec{s, 0, 0}), Level: level},
58+
{Vec: c.Add(Vec{s, s, 0}), Level: level},
59+
{Vec: c.Add(Vec{0, s, 0}), Level: level},
60+
{Vec: c.Add(Vec{0, 0, s}), Level: level},
61+
{Vec: c.Add(Vec{s, 0, s}), Level: level},
62+
{Vec: c.Add(Vec{s, s, s}), Level: level},
63+
{Vec: c.Add(Vec{0, s, s}), Level: level},
6364
}
6465
}
6566

i3/ivec.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ func (a Vec) AddScalar(v int) Vec { return Vec{X: a.X + v, Y: a.Y + v, Z: a.Z +
1313
func (a Vec) MulScalar(v int) Vec { return Vec{X: a.X * v, Y: a.Y * v, Z: a.Z * v} }
1414
func (a Vec) DivScalar(v int) Vec { return Vec{X: a.X / v, Y: a.Y / v, Z: a.Z / v} }
1515

16-
func (a Vec) ShiftRight(lo int) Vec { return Vec{X: a.X >> lo, Y: a.Y >> lo, Z: a.Z >> lo} }
17-
func (a Vec) ShiftLeft(hi int) Vec { return Vec{X: a.X << hi, Y: a.Y << hi, Z: a.Z << hi} }
16+
func (a Vec) ShiftRightScalar(lo int) Vec { return Vec{X: a.X >> lo, Y: a.Y >> lo, Z: a.Z >> lo} }
17+
func (a Vec) ShiftLeftScalar(hi int) Vec { return Vec{X: a.X << hi, Y: a.Y << hi, Z: a.Z << hi} }
1818

1919
func (a Vec) AndScalar(b int) Vec { return Vec{X: a.X & b, Y: a.Y & b, Z: a.Z & b} }
2020
func (a Vec) OrScalar(b int) Vec { return Vec{X: a.X | b, Y: a.Y | b, Z: a.Z | b} }

md3/octree.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func (oct Octree) DecomposeDFS(dst []Vec, cubes []i3.Cube) ([]Vec, []i3.Cube) {
3232
for len(cubes) > 0 {
3333
lastIdx := len(cubes) - 1
3434
cube := cubes[lastIdx]
35-
if cube.Lvl == 0 {
35+
if cube.Level == 0 {
3636
// Cube has been moved to prune queue. Discard and keep going.
3737
cubes = cubes[:lastIdx]
3838
continue
@@ -73,7 +73,7 @@ func (oct Octree) DecomposeBFS(dst []i3.Cube, start i3.Cube, minimumDecomposedLv
7373
}
7474
if cap(dst) < 8 {
7575
return dst, false // No space to decompose new cubes.
76-
} else if start.Lvl <= minimumDecomposedLvl {
76+
} else if start.Level <= minimumDecomposedLvl {
7777
return dst, false // Cube already fully decomposed.
7878
}
7979

@@ -84,7 +84,7 @@ func (oct Octree) DecomposeBFS(dst []i3.Cube, start i3.Cube, minimumDecomposedLv
8484
for cap(dst)-len(dst) >= 8 {
8585
// Decompose and append cubes.
8686
cube := dst[firstIdx]
87-
if cube.Lvl <= minimumDecomposedLvl {
87+
if cube.Level <= minimumDecomposedLvl {
8888
// Reached cube of minimum prunable level.
8989
break
9090
}
@@ -108,15 +108,15 @@ func (oct Octree) SafeMove(dst, src []i3.Cube) (newDst, newSrc []i3.Cube) {
108108
return dst, src
109109
}
110110
// Calculate amount of cubes that would be generated in DFS
111-
srcGenCubes := 8 * (src[0].Lvl + 1) // TODO(soypat): Checking the first cube is very (read as "too") conservative.
112-
neededSpace := 1 + srcGenCubes // plus one for appended cube.
111+
srcGenCubes := 8 * (src[0].Level + 1) // TODO(soypat): Checking the first cube is very (read as "too") conservative.
112+
neededSpace := 1 + srcGenCubes // plus one for appended cube.
113113
// Calculate free space in dst after cubes generated by 1 decomposition+append.
114114
free := cap(dst) - neededSpace
115115
trimIdx := max(0, len(src)-free)
116116
prevCap := cap(dst)
117117
dst = append(dst, src[trimIdx:]...)
118118
if cap(dst) != prevCap {
119-
panic("heapless assumption broken")
119+
panic("heapless promise broken")
120120
}
121121
src = src[:trimIdx]
122122
return dst, src
@@ -125,22 +125,23 @@ func (oct Octree) SafeMove(dst, src []i3.Cube) (newDst, newSrc []i3.Cube) {
125125
// SafeSpread takes cube swith Lvl>0 from end of src and "spreads" them over dstWithLvl0 cube buffer taking special care so that the buffer can still be decomposed to smallest cubes.
126126
// The buffer dstWithLvl0 is considered to have exactly numLvl0 cubes with Lvl==0 anywhere within. These Lvl==0 cubes will be replaced
127127
// with src cubes first.
128+
// src must not contain zero leveled cubes.
128129
func (oct Octree) SafeSpread(dstWithLvl0, src []i3.Cube, numLvl0 int) (newDst, newSrc []i3.Cube, newNumLvl0 int) {
129130
if len(src) == 0 || numLvl0 == 0 || len(dstWithLvl0) == 0 {
130131
return dstWithLvl0, src, numLvl0 // No work to do.
131132
}
132133
srcIdx := len(src) - 1 // Start appending from end of src.
133134
cube := src[srcIdx]
134-
neededSpace := 8*cube.Lvl + 1
135+
neededSpace := 8*cube.Level + 1
135136
for i := 0; numLvl0 > 0 && i < len(dstWithLvl0); i++ {
136137
free := cap(dstWithLvl0) - i
137138
if free < neededSpace {
138139
break // If we add this cube we'd overflow the target buffer upon DFS decomposition, so don't.
139140
}
140141
// Look for zero level cubes (invalid/empty/discarded).
141-
if dstWithLvl0[i].Lvl != 0 {
142+
if dstWithLvl0[i].Level != 0 {
142143
continue
143-
} else if cube.Lvl == 0 {
144+
} else if cube.Level == 0 {
144145
panic("bad src cube in octreeSafeSpread")
145146
}
146147
// Calculate free space.
@@ -151,7 +152,7 @@ func (oct Octree) SafeSpread(dstWithLvl0, src []i3.Cube, numLvl0 int) (newDst, n
151152
break // Done processing cubes.
152153
}
153154
cube = src[srcIdx]
154-
neededSpace = 8*cube.Lvl + 1
155+
neededSpace = 8*cube.Level + 1
155156
}
156157
return dstWithLvl0, src[:srcIdx+1], numLvl0
157158
}
@@ -179,6 +180,13 @@ func (oct Octree) CubeOrigin(c i3.Cube, cubeSize float64) Vec {
179180
return Add(oct.Origin, Scale(cubeSize, Vec{X: float64(idx.X), Y: float64(idx.Y), Z: float64(idx.Z)}))
180181
}
181182

183+
// CubeCenter returns center of cube.
184+
// cubeSize should be the result of [Octree.CubeSize] called on c. It is left to the user for performance reasons.
185+
func (oct Octree) CubeCenter(c i3.Cube, cubeSize float64) Vec {
186+
idx := c.Index()
187+
return Add(oct.Origin, Scale(cubeSize, Vec{X: float64(idx.X) + 0.5, Y: float64(idx.Y) + 0.5, Z: float64(idx.Z) + 0.5}))
188+
}
189+
182190
// Box returns the bounding box of the cube argument.
183191
// cubeSize should be the result of [Octree.CubeSize] called on c. It is left to the user for performance reasons.
184192
func (oct Octree) CubeBox(c i3.Cube, cubeSize float64) Box {
@@ -191,7 +199,7 @@ func (oct Octree) CubeBox(c i3.Cube, cubeSize float64) Box {
191199

192200
// CubeSize returns the length of the sides of the cube.
193201
func (oct Octree) CubeSize(c i3.Cube) float64 {
194-
dim := 1 << (c.Lvl - 1)
202+
dim := 1 << (c.Level - 1)
195203
return float64(dim) * oct.Resolution
196204
}
197205

ms3/octree.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func (oct Octree) DecomposeDFS(dst []Vec, cubes []i3.Cube) ([]Vec, []i3.Cube) {
2828
for len(cubes) > 0 {
2929
lastIdx := len(cubes) - 1
3030
cube := cubes[lastIdx]
31-
if cube.Lvl == 0 {
31+
if cube.Level == 0 {
3232
// Cube has been moved to prune queue. Discard and keep going.
3333
cubes = cubes[:lastIdx]
3434
continue
@@ -69,7 +69,7 @@ func (oct Octree) DecomposeBFS(dst []i3.Cube, start i3.Cube, minimumDecomposedLv
6969
}
7070
if cap(dst) < 8 {
7171
return dst, false // No space to decompose new cubes.
72-
} else if start.Lvl <= minimumDecomposedLvl {
72+
} else if start.Level <= minimumDecomposedLvl {
7373
return dst, false // Cube already fully decomposed.
7474
}
7575

@@ -80,7 +80,7 @@ func (oct Octree) DecomposeBFS(dst []i3.Cube, start i3.Cube, minimumDecomposedLv
8080
for cap(dst)-len(dst) >= 8 {
8181
// Decompose and append cubes.
8282
cube := dst[firstIdx]
83-
if cube.Lvl <= minimumDecomposedLvl {
83+
if cube.Level <= minimumDecomposedLvl {
8484
// Reached cube of minimum prunable level.
8585
break
8686
}
@@ -104,15 +104,15 @@ func (oct Octree) SafeMove(dst, src []i3.Cube) (newDst, newSrc []i3.Cube) {
104104
return dst, src
105105
}
106106
// Calculate amount of cubes that would be generated in DFS
107-
srcGenCubes := 8 * (src[0].Lvl + 1) // TODO(soypat): Checking the first cube is very (read as "too") conservative.
108-
neededSpace := 1 + srcGenCubes // plus one for appended cube.
107+
srcGenCubes := 8 * (src[0].Level + 1) // TODO(soypat): Checking the first cube is very (read as "too") conservative.
108+
neededSpace := 1 + srcGenCubes // plus one for appended cube.
109109
// Calculate free space in dst after cubes generated by 1 decomposition+append.
110110
free := cap(dst) - neededSpace
111111
trimIdx := max(0, len(src)-free)
112112
prevCap := cap(dst)
113113
dst = append(dst, src[trimIdx:]...)
114114
if cap(dst) != prevCap {
115-
panic("heapless assumption broken")
115+
panic("heapless promise broken")
116116
}
117117
src = src[:trimIdx]
118118
return dst, src
@@ -121,22 +121,23 @@ func (oct Octree) SafeMove(dst, src []i3.Cube) (newDst, newSrc []i3.Cube) {
121121
// SafeSpread takes cube swith Lvl>0 from end of src and "spreads" them over dstWithLvl0 cube buffer taking special care so that the buffer can still be decomposed to smallest cubes.
122122
// The buffer dstWithLvl0 is considered to have exactly numLvl0 cubes with Lvl==0 anywhere within. These Lvl==0 cubes will be replaced
123123
// with src cubes first.
124+
// src must not contain zero leveled cubes.
124125
func (oct Octree) SafeSpread(dstWithLvl0, src []i3.Cube, numLvl0 int) (newDst, newSrc []i3.Cube, newNumLvl0 int) {
125126
if len(src) == 0 || numLvl0 == 0 || len(dstWithLvl0) == 0 {
126127
return dstWithLvl0, src, numLvl0 // No work to do.
127128
}
128129
srcIdx := len(src) - 1 // Start appending from end of src.
129130
cube := src[srcIdx]
130-
neededSpace := 8*cube.Lvl + 1
131+
neededSpace := 8*cube.Level + 1
131132
for i := 0; numLvl0 > 0 && i < len(dstWithLvl0); i++ {
132133
free := cap(dstWithLvl0) - i
133134
if free < neededSpace {
134135
break // If we add this cube we'd overflow the target buffer upon DFS decomposition, so don't.
135136
}
136137
// Look for zero level cubes (invalid/empty/discarded).
137-
if dstWithLvl0[i].Lvl != 0 {
138+
if dstWithLvl0[i].Level != 0 {
138139
continue
139-
} else if cube.Lvl == 0 {
140+
} else if cube.Level == 0 {
140141
panic("bad src cube in octreeSafeSpread")
141142
}
142143
// Calculate free space.
@@ -147,7 +148,7 @@ func (oct Octree) SafeSpread(dstWithLvl0, src []i3.Cube, numLvl0 int) (newDst, n
147148
break // Done processing cubes.
148149
}
149150
cube = src[srcIdx]
150-
neededSpace = 8*cube.Lvl + 1
151+
neededSpace = 8*cube.Level + 1
151152
}
152153
return dstWithLvl0, src[:srcIdx+1], numLvl0
153154
}
@@ -175,6 +176,13 @@ func (oct Octree) CubeOrigin(c i3.Cube, cubeSize float32) Vec {
175176
return Add(oct.Origin, Scale(cubeSize, Vec{X: float32(idx.X), Y: float32(idx.Y), Z: float32(idx.Z)}))
176177
}
177178

179+
// CubeCenter returns center of cube.
180+
// cubeSize should be the result of [Octree.CubeSize] called on c. It is left to the user for performance reasons.
181+
func (oct Octree) CubeCenter(c i3.Cube, cubeSize float32) Vec {
182+
halfSize := 0.5 * cubeSize
183+
return Add(oct.CubeOrigin(c, cubeSize), Vec{X: halfSize, Y: halfSize, Z: halfSize})
184+
}
185+
178186
// Box returns the bounding box of the cube argument.
179187
// cubeSize should be the result of [Octree.CubeSize] called on c. It is left to the user for performance reasons.
180188
func (oct Octree) CubeBox(c i3.Cube, cubeSize float32) Box {
@@ -187,7 +195,7 @@ func (oct Octree) CubeBox(c i3.Cube, cubeSize float32) Box {
187195

188196
// CubeSize returns the length of the sides of the cube.
189197
func (oct Octree) CubeSize(c i3.Cube) float32 {
190-
dim := 1 << (c.Lvl - 1)
198+
dim := 1 << (c.Level - 1)
191199
return float32(dim) * oct.Resolution
192200
}
193201

0 commit comments

Comments
 (0)