|
| 1 | +// DO NOT EDIT. |
| 2 | +// This file was generated automatically |
| 3 | +// from gen.go. Please do not edit this file. |
| 4 | + |
| 5 | +package md3 |
| 6 | + |
| 7 | +import ( |
| 8 | + "github.com/soypat/geometry/i3" |
| 9 | +) |
| 10 | + |
| 11 | +// Octree implements heapless 3D spatial Octree algorithms. |
| 12 | +type Octree struct { |
| 13 | + // Resolution is the size of the smallest cube in the Octree. [i3.Cube].Lvl==0. |
| 14 | + Resolution float64 |
| 15 | + // Origin represents the position of the first cube's first corner (most negative/smallest corner). |
| 16 | + Origin Vec |
| 17 | +} |
| 18 | + |
| 19 | +// DecomposeDFS decomposes icubes from the end of cubes into their octree sub-icubes |
| 20 | +// and appends them to the cubes buffer, resulting in a depth-first traversal (DFS) of the octree. |
| 21 | +// This way cubes will contain the largest cubes at the start (low index) and the smallest cubes at the end (high index). |
| 22 | +// Cubes that reach the smallest size will be consumed and their 3D corners appended to dst. Smallest size cubes do not decompose into more icubes. |
| 23 | +// cubes with level of zero are discarded and no action is taken. |
| 24 | +// |
| 25 | +// The icube decomposition continues until one or more of the following conditions are met: |
| 26 | +// - Smallest cube size is reached and the capacity in 3D dst can't store a resolution sized icube corners, calculated as cap(dst)-len(dst) < 64. |
| 27 | +// - Need to decompose a icube to more icubes but capacity of cubes buffer not enough to store an octree decomposition, calculated as cap(cubes)-len(cubes) < 8. |
| 28 | +// - cubes buffer has been fully consumed and is empty, calculated as len(cubes) == 0. |
| 29 | +// |
| 30 | +// This algorithm is HEAPLESS: this means dst and cubes buffer capacities are not modified. |
| 31 | +func (oct Octree) DecomposeDFS(dst []Vec, cubes []i3.Cube) ([]Vec, []i3.Cube) { |
| 32 | + for len(cubes) > 0 { |
| 33 | + lastIdx := len(cubes) - 1 |
| 34 | + cube := cubes[lastIdx] |
| 35 | + if cube.Lvl == 0 { |
| 36 | + // Cube has been moved to prune queue. Discard and keep going. |
| 37 | + cubes = cubes[:lastIdx] |
| 38 | + continue |
| 39 | + } |
| 40 | + if cube.IsSecondSmallest() { |
| 41 | + // Is base-level cube. |
| 42 | + if cap(dst)-len(dst) < 8*8 { |
| 43 | + break // No space for position buffering. |
| 44 | + } |
| 45 | + subcubes := cube.Octree() |
| 46 | + for _, scube := range subcubes { |
| 47 | + corners := oct.CubeCorners(scube, oct.Resolution) |
| 48 | + // corners := scube.(origin, res) |
| 49 | + dst = append(dst, corners[:]...) |
| 50 | + } |
| 51 | + cubes = cubes[:lastIdx] // Trim cube used. |
| 52 | + |
| 53 | + } else { |
| 54 | + // Is cube with sub-cubes. |
| 55 | + if cap(cubes)-len(cubes) < 8 { |
| 56 | + break // No more space for cube buffering. |
| 57 | + } |
| 58 | + subcubes := cube.Octree() |
| 59 | + // We trim off the last cube which we just processed in append. |
| 60 | + cubes = append(cubes[:lastIdx], subcubes[:]...) |
| 61 | + } |
| 62 | + } |
| 63 | + return dst, cubes |
| 64 | +} |
| 65 | + |
| 66 | +// DecomposeBFS decomposes start into octree cubes and appends them to dst without surpassing dst's slice capacity |
| 67 | +// and continues to decompose the resulting cubes until all cubes are minimumDecomposedLvl or dst capacity reached. |
| 68 | +// Smallest cubes will remain at the highest index of dst. The boolean value returned indicates whether the |
| 69 | +// argument start icube was able to be decomposed and its children added to dst. |
| 70 | +func (oct Octree) DecomposeBFS(dst []i3.Cube, start i3.Cube, minimumDecomposedLvl int) ([]i3.Cube, bool) { |
| 71 | + if minimumDecomposedLvl < 1 { |
| 72 | + panic("bad minimumDecomposedLvl") |
| 73 | + } |
| 74 | + if cap(dst) < 8 { |
| 75 | + return dst, false // No space to decompose new cubes. |
| 76 | + } else if start.Lvl <= minimumDecomposedLvl { |
| 77 | + return dst, false // Cube already fully decomposed. |
| 78 | + } |
| 79 | + |
| 80 | + subCubes := start.Octree() |
| 81 | + startIdx := len(dst) |
| 82 | + firstIdx := len(dst) |
| 83 | + dst = append(dst, subCubes[:]...) // Cubes will be of at minimum minLvl-1 |
| 84 | + for cap(dst)-len(dst) >= 8 { |
| 85 | + // Decompose and append cubes. |
| 86 | + cube := dst[firstIdx] |
| 87 | + if cube.Lvl <= minimumDecomposedLvl { |
| 88 | + // Reached cube of minimum prunable level. |
| 89 | + break |
| 90 | + } |
| 91 | + subCubes := cube.Octree() |
| 92 | + // Is cube with sub-cubes. |
| 93 | + // We trim off the last cube which we just processed in append. |
| 94 | + dst = append(dst, subCubes[:]...) |
| 95 | + firstIdx++ |
| 96 | + } |
| 97 | + // Move cubes to start of buffer from where we started consuming them. |
| 98 | + n := copy(dst[startIdx:], dst[firstIdx:]) |
| 99 | + dst = dst[:startIdx+n] |
| 100 | + return dst, true |
| 101 | +} |
| 102 | + |
| 103 | +// SafeMove appends cubes from the end of src to dst while taking care |
| 104 | +// not to leave dst without space to decompose to smallest cube level using DFS. |
| 105 | +// Cubes appended to dst from src are removed from src. |
| 106 | +func (oct Octree) SafeMove(dst, src []i3.Cube) (newDst, newSrc []i3.Cube) { |
| 107 | + if len(src) == 0 { |
| 108 | + return dst, src |
| 109 | + } |
| 110 | + // 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. |
| 113 | + // Calculate free space in dst after cubes generated by 1 decomposition+append. |
| 114 | + free := cap(dst) - neededSpace |
| 115 | + trimIdx := max(0, len(src)-free) |
| 116 | + prevCap := cap(dst) |
| 117 | + dst = append(dst, src[trimIdx:]...) |
| 118 | + if cap(dst) != prevCap { |
| 119 | + panic("heapless assumption broken") |
| 120 | + } |
| 121 | + src = src[:trimIdx] |
| 122 | + return dst, src |
| 123 | +} |
| 124 | + |
| 125 | +// 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. |
| 126 | +// The buffer dstWithLvl0 is considered to have exactly numLvl0 cubes with Lvl==0 anywhere within. These Lvl==0 cubes will be replaced |
| 127 | +// with src cubes first. |
| 128 | +func (oct Octree) SafeSpread(dstWithLvl0, src []i3.Cube, numLvl0 int) (newDst, newSrc []i3.Cube, newNumLvl0 int) { |
| 129 | + if len(src) == 0 || numLvl0 == 0 || len(dstWithLvl0) == 0 { |
| 130 | + return dstWithLvl0, src, numLvl0 // No work to do. |
| 131 | + } |
| 132 | + srcIdx := len(src) - 1 // Start appending from end of src. |
| 133 | + cube := src[srcIdx] |
| 134 | + neededSpace := 8*cube.Lvl + 1 |
| 135 | + for i := 0; numLvl0 > 0 && i < len(dstWithLvl0); i++ { |
| 136 | + free := cap(dstWithLvl0) - i |
| 137 | + if free < neededSpace { |
| 138 | + break // If we add this cube we'd overflow the target buffer upon DFS decomposition, so don't. |
| 139 | + } |
| 140 | + // Look for zero level cubes (invalid/empty/discarded). |
| 141 | + if dstWithLvl0[i].Lvl != 0 { |
| 142 | + continue |
| 143 | + } else if cube.Lvl == 0 { |
| 144 | + panic("bad src cube in octreeSafeSpread") |
| 145 | + } |
| 146 | + // Calculate free space. |
| 147 | + dstWithLvl0[i] = cube |
| 148 | + numLvl0-- |
| 149 | + srcIdx-- |
| 150 | + if srcIdx < 0 { |
| 151 | + break // Done processing cubes. |
| 152 | + } |
| 153 | + cube = src[srcIdx] |
| 154 | + neededSpace = 8*cube.Lvl + 1 |
| 155 | + } |
| 156 | + return dstWithLvl0, src[:srcIdx+1], numLvl0 |
| 157 | +} |
| 158 | + |
| 159 | +// CubeCorners returns the corners of the cube starting with the z=0 xy plane corners. |
| 160 | +// cubeSize should be the result of [Octree.CubeSize] called on c. It is left to the user to do for performance reasons. |
| 161 | +func (oct Octree) CubeCorners(c i3.Cube, cubeSize float64) [8]Vec { |
| 162 | + origin := oct.CubeOrigin(c, cubeSize) |
| 163 | + return [8]Vec{ |
| 164 | + Add(origin, Vec{X: 0, Y: 0, Z: 0}), |
| 165 | + Add(origin, Vec{X: cubeSize, Y: 0, Z: 0}), |
| 166 | + Add(origin, Vec{X: cubeSize, Y: cubeSize, Z: 0}), |
| 167 | + Add(origin, Vec{X: 0, Y: cubeSize, Z: 0}), |
| 168 | + Add(origin, Vec{X: 0, Y: 0, Z: cubeSize}), |
| 169 | + Add(origin, Vec{X: cubeSize, Y: 0, Z: cubeSize}), |
| 170 | + Add(origin, Vec{X: cubeSize, Y: cubeSize, Z: cubeSize}), |
| 171 | + Add(origin, Vec{X: 0, Y: cubeSize, Z: cubeSize}), |
| 172 | + } |
| 173 | +} |
| 174 | + |
| 175 | +// CubeOrigin returns the Cube argument origin (lowest index corner position) in the octree. |
| 176 | +// cubeSize should be the result of [Octree.CubeSize] called on c. It is left to the user to do for performance reasons. |
| 177 | +func (oct Octree) CubeOrigin(c i3.Cube, cubeSize float64) Vec { |
| 178 | + idx := c.Index() |
| 179 | + return Add(oct.Origin, Scale(cubeSize, Vec{X: float64(idx.X), Y: float64(idx.Y), Z: float64(idx.Z)})) |
| 180 | +} |
| 181 | + |
| 182 | +// CubeSize returns the length of the sides of the cube. |
| 183 | +func (oct Octree) CubeSize(c i3.Cube) float64 { |
| 184 | + dim := 1 << (c.Lvl - 1) |
| 185 | + return float64(dim) * oct.Resolution |
| 186 | +} |
| 187 | + |
| 188 | +func max(a, b int) int { |
| 189 | + if a > b { |
| 190 | + return a |
| 191 | + } |
| 192 | + return b |
| 193 | +} |
0 commit comments