Skip to content

Commit a97a8d0

Browse files
authored
RSDK-10392 remove magic numbers from point cloud collision check (#4896)
1 parent d8cb372 commit a97a8d0

11 files changed

+119
-144
lines changed

motionplan/constraint.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func createAllCollisionConstraints(
117117
for _, geom := range worldGeometries {
118118
if octree, ok := geom.(*pointcloud.BasicOctree); ok {
119119
if zeroCG == nil {
120-
zeroCG, err = setupZeroCG(movingRobotGeometries, worldGeometries, allowedCollisions, collisionBufferMM)
120+
zeroCG, err = setupZeroCG(movingRobotGeometries, worldGeometries, allowedCollisions, false, collisionBufferMM)
121121
if err != nil {
122122
return nil, nil, err
123123
}
@@ -193,12 +193,14 @@ func createAllCollisionConstraints(
193193
return constraintFSMap, constraintMap, nil
194194
}
195195

196-
func setupZeroCG(moving, static []spatial.Geometry,
196+
func setupZeroCG(
197+
moving, static []spatial.Geometry,
197198
collisionSpecifications []*Collision,
199+
reportDistances bool,
198200
collisionBufferMM float64,
199201
) (*collisionGraph, error) {
200202
// create the reference collisionGraph
201-
zeroCG, err := newCollisionGraph(moving, static, nil, true, collisionBufferMM)
203+
zeroCG, err := newCollisionGraph(moving, static, nil, reportDistances, collisionBufferMM)
202204
if err != nil {
203205
return nil, err
204206
}
@@ -218,7 +220,7 @@ func NewCollisionConstraint(
218220
reportDistances bool,
219221
collisionBufferMM float64,
220222
) (StateConstraint, error) {
221-
zeroCG, err := setupZeroCG(moving, static, collisionSpecifications, collisionBufferMM)
223+
zeroCG, err := setupZeroCG(moving, static, collisionSpecifications, true, collisionBufferMM)
222224
if err != nil {
223225
return nil, err
224226
}
@@ -273,7 +275,7 @@ func NewCollisionConstraintFS(
273275
reportDistances bool,
274276
collisionBufferMM float64,
275277
) (StateFSConstraint, error) {
276-
zeroCG, err := setupZeroCG(moving, static, collisionSpecifications, collisionBufferMM)
278+
zeroCG, err := setupZeroCG(moving, static, collisionSpecifications, true, collisionBufferMM)
277279
if err != nil {
278280
return nil, err
279281
}

pointcloud/basic_octree.go

Lines changed: 29 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,10 @@ const (
1717
leafNodeFilled
1818
// This value allows for high level of granularity in the octree while still allowing for fast access times
1919
// even on a pi.
20-
maxRecursionDepth = 250 // This gives us enough resolution to model the observable universe in planck lengths.
21-
floatEpsilon = 1e-6 // This is also effectively half of the minimum side length.
22-
nodeRegionOverlap = floatEpsilon / 2
23-
// TODO (RSDK-3767): pass these in a different way.
24-
confidenceThreshold = 50 // value between 0-100, threshold sets the confidence level required for a point to be considered a collision
25-
buffer = 150.0 // max distance from base to point for it to be considered a collision in mm
20+
maxRecursionDepth = 250 // This gives us enough resolution to model the observable universe in planck lengths.
21+
floatEpsilon = 1e-6 // This is also effectively half of the minimum side length.
22+
nodeRegionOverlap = floatEpsilon / 2
23+
defaultConfidenceThreshold = 50
2624
)
2725

2826
// NodeType represents the possible types of nodes in an octree.
@@ -39,6 +37,9 @@ type BasicOctree struct {
3937
size int
4038
meta MetaData
4139
label string
40+
41+
// value between 0-100 that sets a threshold which is the confidence level required for a point to be considered a collision
42+
confidenceThreshold int
4243
}
4344

4445
// basicOctreeNode is a struct comprised of the type of node, children nodes (should they exist) and the pointcloud's
@@ -50,21 +51,19 @@ type basicOctreeNode struct {
5051
maxVal int
5152
}
5253

53-
// NewBasicOctree creates a new basic octree with specified center, side and metadata.
54-
func NewBasicOctree(center r3.Vector, sideLength float64) (*BasicOctree, error) {
54+
// NewBasicOctree creates a new basic octree with specified center, side and confidenceThreshold.
55+
func NewBasicOctree(center r3.Vector, sideLength float64, confidenceThreshold int) (*BasicOctree, error) {
5556
if sideLength <= 0 {
5657
return nil, errors.Errorf("invalid side length (%.2f) for octree", sideLength)
5758
}
58-
59-
octree := &BasicOctree{
60-
node: newLeafNodeEmpty(),
61-
center: center,
62-
sideLength: sideLength,
63-
size: 0,
64-
meta: NewMetaData(),
65-
}
66-
67-
return octree, nil
59+
return &BasicOctree{
60+
node: newLeafNodeEmpty(),
61+
center: center,
62+
sideLength: sideLength,
63+
size: 0,
64+
meta: NewMetaData(),
65+
confidenceThreshold: confidenceThreshold,
66+
}, nil
6867
}
6968

7069
// Size returns the number of points stored in the octree's metadata.
@@ -174,7 +173,7 @@ func (octree *BasicOctree) Transform(pose spatialmath.Pose) spatialmath.Geometry
174173
newCenter := spatialmath.Compose(pose, spatialmath.NewPoseFromPoint(octree.center))
175174

176175
// New sidelength is the diagonal of octree to guarantee fit
177-
newOctree, err := NewBasicOctree(newCenter.Point(), octree.sideLength*math.Sqrt(3))
176+
newOctree, err := NewBasicOctree(newCenter.Point(), octree.sideLength*math.Sqrt(3), octree.confidenceThreshold)
178177
if err != nil {
179178
return nil
180179
}
@@ -198,22 +197,21 @@ func (octree *BasicOctree) ToProtobuf() *commonpb.Geometry {
198197
return nil
199198
}
200199

201-
// CollidesWithGeometry will return whether a given geometry is in collision with a given point.
202-
// A point is in collision if its stored probability is >= confidenceThreshold and if it is at most buffer distance away.
203-
func (octree *BasicOctree) CollidesWithGeometry(
204-
geom spatialmath.Geometry,
205-
confidenceThreshold int,
206-
buffer,
207-
collisionBufferMM float64,
208-
) (bool, error) {
209-
if octree.MaxVal() < confidenceThreshold {
200+
// CollidesWith checks if the given octree collides with the given geometry and returns true if it does.
201+
// A point is in collision if its stored probability is >= confidenceThreshold and if it is at most collisionBufferMM distance away.
202+
func (octree *BasicOctree) CollidesWith(geom spatialmath.Geometry, collisionBufferMM float64) (bool, error) {
203+
if octree.MaxVal() < octree.confidenceThreshold {
210204
return false, nil
211205
}
212206
switch octree.node.nodeType {
213207
case internalNode:
214208
ocbox, err := spatialmath.NewBox(
215209
spatialmath.NewPoseFromPoint(octree.center),
216-
r3.Vector{octree.sideLength + buffer, octree.sideLength + buffer, octree.sideLength + buffer},
210+
r3.Vector{
211+
X: octree.sideLength + collisionBufferMM,
212+
Y: octree.sideLength + collisionBufferMM,
213+
Z: octree.sideLength + collisionBufferMM,
214+
},
217215
"",
218216
)
219217
if err != nil {
@@ -229,7 +227,7 @@ func (octree *BasicOctree) CollidesWithGeometry(
229227
return false, nil
230228
}
231229
for _, child := range octree.node.children {
232-
collide, err = child.CollidesWithGeometry(geom, confidenceThreshold, buffer, collisionBufferMM)
230+
collide, err = child.CollidesWith(geom, collisionBufferMM)
233231
if err != nil {
234232
return false, err
235233
}
@@ -241,25 +239,11 @@ func (octree *BasicOctree) CollidesWithGeometry(
241239
case leafNodeEmpty:
242240
return false, nil
243241
case leafNodeFilled:
244-
ptGeom, err := spatialmath.NewSphere(spatialmath.NewPoseFromPoint(octree.node.point.P), buffer, "")
245-
if err != nil {
246-
return false, err
247-
}
248-
249-
ptCollide, err := geom.CollidesWith(ptGeom, collisionBufferMM)
250-
if err != nil {
251-
return false, err
252-
}
253-
return ptCollide, nil
242+
return geom.CollidesWith(spatialmath.NewPoint(octree.node.point.P, ""), collisionBufferMM)
254243
}
255244
return false, errors.New("unknown octree node type")
256245
}
257246

258-
// CollidesWith checks if the given octree collides with the given geometry and returns true if it does.
259-
func (octree *BasicOctree) CollidesWith(geom spatialmath.Geometry, collisionBufferMM float64) (bool, error) {
260-
return octree.CollidesWithGeometry(geom, confidenceThreshold, buffer, collisionBufferMM)
261-
}
262-
263247
// DistanceFrom returns the distance from the given octree to the given geometry.
264248
// TODO (RSDK-3743): Implement BasicOctree Geometry functions.
265249
func (octree *BasicOctree) DistanceFrom(geom spatialmath.Geometry) (float64, error) {

pointcloud/basic_octree_test.go

Lines changed: 26 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717

1818
// Helper function for generating a new empty octree.
1919
func createNewOctree(center r3.Vector, side float64) (*BasicOctree, error) {
20-
basicOct, err := NewBasicOctree(center, side)
20+
basicOct, err := NewBasicOctree(center, side, defaultConfidenceThreshold)
2121
if err != nil {
2222
return nil, err
2323
}
@@ -98,6 +98,10 @@ func TestBasicOctreeNew(t *testing.T) {
9898
func TestBasicOctreeSet(t *testing.T) {
9999
center := r3.Vector{X: 0, Y: 0, Z: 0}
100100
side := 2.0
101+
d1 := 99
102+
d2 := 100
103+
d3 := 75
104+
d4 := 10
101105

102106
t.Run("Set point into empty leaf node into basic octree", func(t *testing.T) {
103107
basicOct, err := createNewOctree(center, side)
@@ -119,17 +123,15 @@ func TestBasicOctreeSet(t *testing.T) {
119123
basicOct, err := createNewOctree(center, side)
120124
test.That(t, err, test.ShouldBeNil)
121125

122-
d1 := 1
123126
err = basicOct.Set(r3.Vector{X: 0, Y: 0, Z: 0}, NewValueData(d1))
124127
test.That(t, err, test.ShouldBeNil)
125128
mp := basicOct.MaxVal()
126129
test.That(t, mp, test.ShouldEqual, d1)
127130

128-
d2 := 2
129131
err = basicOct.Set(r3.Vector{X: -.5, Y: 0, Z: 0}, NewValueData(d2))
130132
test.That(t, err, test.ShouldBeNil)
131133
test.That(t, basicOct.node.nodeType, test.ShouldResemble, internalNode)
132-
test.That(t, basicOct.Size(), test.ShouldEqual, 2)
134+
test.That(t, basicOct.Size(), test.ShouldEqual, side)
133135
mp = basicOct.node.children[0].MaxVal()
134136
test.That(t, mp, test.ShouldEqual, int(math.Max(float64(d1), float64(d2))))
135137
mp = basicOct.MaxVal()
@@ -142,19 +144,16 @@ func TestBasicOctreeSet(t *testing.T) {
142144
basicOct, err := createNewOctree(center, side)
143145
test.That(t, err, test.ShouldBeNil)
144146

145-
d3 := 3
146147
err = basicOct.Set(r3.Vector{X: 0, Y: 0, Z: 0}, NewValueData(d3))
147148
test.That(t, err, test.ShouldBeNil)
148149
mp := basicOct.MaxVal()
149150
test.That(t, mp, test.ShouldEqual, d3)
150151

151-
d2 := 2
152152
err = basicOct.Set(r3.Vector{X: -.5, Y: 0, Z: 0}, NewValueData(d2))
153153
test.That(t, err, test.ShouldBeNil)
154154
mp = basicOct.node.children[0].MaxVal()
155155
test.That(t, mp, test.ShouldEqual, int(math.Max(float64(d2), float64(d3))))
156156

157-
d4 := 4
158157
err = basicOct.Set(r3.Vector{X: -.4, Y: 0, Z: 0}, NewValueData(d4))
159158
test.That(t, err, test.ShouldBeNil)
160159
test.That(t, basicOct.node.nodeType, test.ShouldResemble, internalNode)
@@ -180,13 +179,11 @@ func TestBasicOctreeSet(t *testing.T) {
180179
basicOct, err := createNewOctree(center, side)
181180
test.That(t, err, test.ShouldBeNil)
182181

183-
d1 := 1
184182
err = basicOct.Set(r3.Vector{X: 0, Y: 0, Z: 0}, NewValueData(d1))
185183
test.That(t, err, test.ShouldBeNil)
186184
mp := basicOct.MaxVal()
187185
test.That(t, mp, test.ShouldEqual, d1)
188186

189-
d2 := 2
190187
err = basicOct.Set(r3.Vector{X: -.5, Y: 0, Z: 0}, NewValueData(d2))
191188
test.That(t, err, test.ShouldBeNil)
192189
test.That(t, basicOct.size, test.ShouldEqual, 2)
@@ -200,14 +197,12 @@ func TestBasicOctreeSet(t *testing.T) {
200197
basicOct, err := createNewOctree(center, side)
201198
test.That(t, err, test.ShouldBeNil)
202199

203-
d1 := 1
204200
err = basicOct.Set(r3.Vector{X: 0, Y: 0, Z: 0}, NewValueData(d1))
205201
test.That(t, err, test.ShouldBeNil)
206202
test.That(t, basicOct.node.point.D.Value(), test.ShouldEqual, d1)
207203
mp := basicOct.MaxVal()
208204
test.That(t, mp, test.ShouldEqual, d1)
209205

210-
d2 := 2
211206
err = basicOct.Set(r3.Vector{X: 0, Y: 0, Z: 0}, NewValueData(d2))
212207
test.That(t, err, test.ShouldBeNil)
213208
test.That(t, basicOct.node.point.D.Value(), test.ShouldEqual, d2)
@@ -243,7 +238,6 @@ func TestBasicOctreeSet(t *testing.T) {
243238

244239
basicOct = createLopsidedOctree(basicOct, 0, maxRecursionDepth-1)
245240

246-
d1 := 1
247241
err = basicOct.Set(r3.Vector{X: -1, Y: -1, Z: -1}, NewValueData(d1))
248242
test.That(t, err, test.ShouldBeNil)
249243
mp := basicOct.MaxVal()
@@ -296,9 +290,9 @@ func TestBasicOctreeAt(t *testing.T) {
296290
test.That(t, err, test.ShouldBeNil)
297291

298292
pointsAndData := []PointAndData{
299-
{P: r3.Vector{X: 0, Y: 0, Z: 0}, D: NewValueData(1)},
300-
{P: r3.Vector{X: -.5, Y: 0, Z: 0}, D: NewValueData(2)},
301-
{P: r3.Vector{X: -0.4, Y: 0, Z: 0}, D: NewValueData(3)},
293+
{P: r3.Vector{X: 0, Y: 0, Z: 0}, D: NewValueData(51)},
294+
{P: r3.Vector{X: -.5, Y: 0, Z: 0}, D: NewValueData(52)},
295+
{P: r3.Vector{X: -0.4, Y: 0, Z: 0}, D: NewValueData(53)},
302296
}
303297

304298
err = addPoints(basicOct, pointsAndData)
@@ -393,9 +387,9 @@ func TestBasicOctreeIterate(t *testing.T) {
393387
test.That(t, err, test.ShouldBeNil)
394388

395389
pointsAndData := []PointAndData{
396-
{P: r3.Vector{X: 0, Y: 0, Z: 0}, D: NewValueData(1)},
397-
{P: r3.Vector{X: .5, Y: 0, Z: 0}, D: NewValueData(2)},
398-
{P: r3.Vector{X: .6, Y: 0, Z: 0}, D: NewValueData(1)},
390+
{P: r3.Vector{X: 0, Y: 0, Z: 0}, D: NewValueData(50)},
391+
{P: r3.Vector{X: .5, Y: 0, Z: 0}, D: NewValueData(51)},
392+
{P: r3.Vector{X: .6, Y: 0, Z: 0}, D: NewValueData(50)},
399393
}
400394

401395
err = addPoints(basicOct, pointsAndData)
@@ -414,7 +408,7 @@ func TestBasicOctreeIterate(t *testing.T) {
414408
// Partial iteration - applies function to only first point
415409
total = 0
416410
basicOct.Iterate(0, 0, func(p r3.Vector, d Data) bool {
417-
if d.Value() == 1 {
411+
if d.Value() == pointsAndData[0].D.Value() {
418412
total += d.Value()
419413
return true
420414
}
@@ -570,7 +564,7 @@ func TestBasicOctreePointcloudIngestion(t *testing.T) {
570564
center := getCenterFromPcMetaData(startPC.MetaData())
571565
maxSideLength := getMaxSideLengthFromPcMetaData(startPC.MetaData())
572566

573-
basicOct, err := NewBasicOctree(center, maxSideLength)
567+
basicOct, err := NewBasicOctree(center, maxSideLength, defaultConfidenceThreshold)
574568
test.That(t, err, test.ShouldBeNil)
575569

576570
startPC.Iterate(0, 0, func(p r3.Vector, d Data) bool {
@@ -633,21 +627,21 @@ func testPCDToBasicOctree(t *testing.T, artifactPath string) {
633627
validateBasicOctree(t, basicOct, basicOct.center, basicOct.sideLength)
634628
}
635629

636-
func createPopulatedOctree(sign int) (*BasicOctree, error) {
630+
func createExampleOctree() (*BasicOctree, error) {
637631
center := r3.Vector{X: 0, Y: 0, Z: 0}
638632
side := 2.0
639633
octree, err := createNewOctree(center, side)
640634
if err != nil {
641635
return nil, err
642636
}
643637
pointsAndData := []PointAndData{
644-
{P: r3.Vector{X: 0, Y: 0, Z: 0}, D: NewValueData(2 * sign)},
645-
{P: r3.Vector{X: .5, Y: 0, Z: 0}, D: NewValueData(3 * sign)},
646-
{P: r3.Vector{X: .5, Y: 0, Z: .5}, D: NewValueData(10 * sign)},
647-
{P: r3.Vector{X: .5, Y: .5, Z: 0}, D: NewValueData(1 * sign)},
648-
{P: r3.Vector{X: .55, Y: .55, Z: 0}, D: NewValueData(4 * sign)},
649-
{P: r3.Vector{X: -.55, Y: -.55, Z: 0}, D: NewValueData(5 * sign)},
650-
{P: r3.Vector{X: .755, Y: .755, Z: 0}, D: NewValueData(6 * sign)},
638+
{P: r3.Vector{X: 0, Y: 0, Z: 0}, D: NewValueData(52)},
639+
{P: r3.Vector{X: .5, Y: 0, Z: 0}, D: NewValueData(53)},
640+
{P: r3.Vector{X: .5, Y: 0, Z: .5}, D: NewValueData(60)},
641+
{P: r3.Vector{X: .5, Y: .5, Z: 0}, D: NewValueData(51)},
642+
{P: r3.Vector{X: .55, Y: .55, Z: 0}, D: NewValueData(54)},
643+
{P: r3.Vector{X: -.55, Y: -.55, Z: 0}, D: NewValueData(55)},
644+
{P: r3.Vector{X: .755, Y: .755, Z: 0}, D: NewValueData(56)},
651645
}
652646

653647
err = addPoints(octree, pointsAndData)
@@ -659,16 +653,16 @@ func createPopulatedOctree(sign int) (*BasicOctree, error) {
659653

660654
func TestCachedMaxProbability(t *testing.T) {
661655
t.Run("get the max val from an octree", func(t *testing.T) {
662-
octree, err := createPopulatedOctree(1)
656+
octree, err := createExampleOctree()
663657
test.That(t, err, test.ShouldBeNil)
664658

665659
validateBasicOctree(t, octree, octree.center, octree.sideLength)
666660

667661
mp := octree.MaxVal()
668-
test.That(t, mp, test.ShouldEqual, 10)
662+
test.That(t, mp, test.ShouldEqual, 60)
669663

670664
mp = octree.node.children[0].MaxVal()
671-
test.That(t, mp, test.ShouldEqual, 5)
665+
test.That(t, mp, test.ShouldEqual, 55)
672666
})
673667

674668
t.Run("cannot set arbitrary values into the octree", func(t *testing.T) {
@@ -678,23 +672,10 @@ func TestCachedMaxProbability(t *testing.T) {
678672
children: nil,
679673
nodeType: leafNodeFilled,
680674
point: &PointAndData{P: r3.Vector{1, 2, 3}, D: d},
681-
maxVal: emptyProb,
675+
maxVal: defaultConfidenceThreshold,
682676
}
683677
test.That(t, node, test.ShouldResemble, filledNode)
684678
})
685-
686-
t.Run("setting negative values", func(t *testing.T) {
687-
octree, err := createPopulatedOctree(-1)
688-
test.That(t, err, test.ShouldBeNil)
689-
690-
validateBasicOctree(t, octree, octree.center, octree.sideLength)
691-
692-
mp := octree.MaxVal()
693-
test.That(t, mp, test.ShouldEqual, -1)
694-
695-
mp = octree.node.children[0].MaxVal()
696-
test.That(t, mp, test.ShouldEqual, -2)
697-
})
698679
}
699680

700681
// Test the various geometry-specific interface methods.

0 commit comments

Comments
 (0)