Skip to content

Commit be31cfa

Browse files
biotinkerraybjork
andauthored
RSDK-9587 generate series of waypoints to cover surface given a pcd mesh (#4719)
Co-authored-by: Ray Bjorkman <[email protected]>
1 parent 666e569 commit be31cfa

File tree

11 files changed

+139
-159
lines changed

11 files changed

+139
-159
lines changed

motionplan/cBiRRT.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,12 @@ func (mp *cBiRRTMotionPlanner) rrtBackgroundRunner(
142142
break
143143
}
144144
}
145-
mp.logger.CInfof(ctx, "goal node: %v\n", rrt.maps.optNode.Q())
145+
mp.logger.CDebugf(ctx, "goal node: %v\n", rrt.maps.optNode.Q())
146+
for n := range rrt.maps.startMap {
147+
mp.logger.CDebugf(ctx, "start node: %v\n", n.Q())
148+
break
149+
}
150+
mp.logger.Debug("DOF", mp.lfs.dof)
146151
interpConfig, err := referenceframe.InterpolateFS(mp.fs, seed, rrt.maps.optNode.Q(), 0.5)
147152
if err != nil {
148153
rrt.solutionChan <- &rrtSolution{err: err}

motionplan/motionPlanner.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import (
2121
"go.viam.com/rdk/spatialmath"
2222
)
2323

24+
// When we generate solutions, if a new solution is within this level of similarity to an existing one, discard it as a duplicate.
25+
// This prevents seeding the solution tree with 50 copies of essentially the same configuration.
26+
const defaultSimScore = 0.05
27+
2428
// motionPlanner provides an interface to path planning methods, providing ways to request a path to be planned, and
2529
// management of the constraints used to plan paths.
2630
type motionPlanner interface {
@@ -464,6 +468,17 @@ IK:
464468
// good solution, stopping early
465469
break IK
466470
}
471+
for _, oldSol := range solutions {
472+
similarity := &ik.SegmentFS{
473+
StartConfiguration: oldSol,
474+
EndConfiguration: step,
475+
FS: mp.fs,
476+
}
477+
simscore := mp.planOpts.configurationDistanceFunc(similarity)
478+
if simscore < defaultSimScore {
479+
continue IK
480+
}
481+
}
467482

468483
solutions[score] = step
469484
if len(solutions) >= nSolutions {

motionplan/planManager.go

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,6 @@ type atomicWaypoint struct {
6363
// planMultiWaypoint plans a motion through multiple waypoints, using identical constraints for each
6464
// Any constraints, etc, will be held for the entire motion.
6565
func (pm *planManager) planMultiWaypoint(ctx context.Context, request *PlanRequest, seedPlan Plan) (Plan, error) {
66-
startPoses, err := request.StartState.ComputePoses(request.FrameSystem)
67-
if err != nil {
68-
return nil, err
69-
}
70-
7166
opt, err := pm.plannerSetupFromMoveRequest(
7267
request.StartState,
7368
request.Goals[0],
@@ -101,26 +96,12 @@ func (pm *planManager) planMultiWaypoint(ctx context.Context, request *PlanReque
10196

10297
waypoints := []atomicWaypoint{}
10398

104-
runningStart := startPoses
105-
for i, goal := range request.Goals {
106-
goalPoses, err := goal.ComputePoses(request.FrameSystem)
107-
if err != nil {
108-
return nil, err
109-
}
110-
111-
// Log each requested motion
112-
// TODO: PlanRequest.String() could begin to exist
113-
for frame, stepgoal := range goalPoses {
114-
request.Logger.CInfof(ctx,
115-
"setting up motion for frame %s\nGoal: %v\nstartPose %v\nworldstate: %v\n",
116-
frame,
117-
referenceframe.PoseInFrameToProtobuf(stepgoal),
118-
referenceframe.PoseInFrameToProtobuf(runningStart[frame]),
119-
request.WorldState.String(),
120-
)
99+
for i := range request.Goals {
100+
select {
101+
case <-ctx.Done():
102+
return nil, ctx.Err()
103+
default:
121104
}
122-
runningStart = goalPoses
123-
124105
// Solving highly constrained motions by breaking apart into small pieces is much more performant
125106
goalWaypoints, err := pm.generateWaypoints(request, seedPlan, i)
126107
if err != nil {
@@ -156,13 +137,17 @@ func (pm *planManager) planAtomicWaypoints(
156137
var seed referenceframe.FrameSystemInputs
157138

158139
// try to solve each goal, one at a time
159-
for _, wp := range waypoints {
140+
for i, wp := range waypoints {
160141
// Check if ctx is done between each waypoint
161142
select {
162143
case <-ctx.Done():
163144
return nil, ctx.Err()
164145
default:
165146
}
147+
pm.logger.Info("planning step ", i, " of ", len(waypoints), ":", wp.goalState)
148+
for k, v := range wp.goalState.Poses() {
149+
pm.logger.Info(k, v)
150+
}
166151

167152
var maps *rrtMaps
168153
if seedPlan != nil {
@@ -227,7 +212,16 @@ func (pm *planManager) planSingleAtomicWaypoint(
227212
wp atomicWaypoint,
228213
maps *rrtMaps,
229214
) (referenceframe.FrameSystemInputs, *resultPromise, error) {
230-
pm.logger.Debug("start planning for ", wp.goalState.configuration, wp.goalState.poses)
215+
fromPoses, err := wp.startState.ComputePoses(pm.fs)
216+
if err != nil {
217+
return nil, nil, err
218+
}
219+
toPoses, err := wp.goalState.ComputePoses(pm.fs)
220+
if err != nil {
221+
return nil, nil, err
222+
}
223+
pm.logger.Debug("start configuration", wp.startState.Configuration())
224+
pm.logger.Debug("start planning from\n", fromPoses, "\nto\n", toPoses)
231225

232226
if _, ok := wp.mp.(rrtParallelPlanner); ok {
233227
// rrtParallelPlanner supports solution look-ahead for parallel waypoint solving

motionplan/plannerOptions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const (
2727
defaultPseudolinearTolerance = 0.8
2828

2929
// Number of IK solutions that should be generated before stopping.
30-
defaultSolutionsToSeed = 50
30+
defaultSolutionsToSeed = 100
3131

3232
// Check constraints are still met every this many mm/degrees of movement.
3333
defaultResolution = 2.0

services/motion/builtin/builtin.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,12 @@ func (ms *builtIn) plan(ctx context.Context, req motion.MoveReq) (motionplan.Pla
417417
if len(waypoints) == 0 {
418418
return nil, errors.New("could not find any waypoints to plan for in MoveRequest. Fill in Destination or goal_state")
419419
}
420+
// The contents of waypoints can be gigantic, and if so, making copies of `extra` becomes the majority of motion planning runtime.
421+
// As the meaning from `waypoints` has already been extracted above into its proper data structure, there is no longer a need to
422+
// keep it in `extra`.
423+
if req.Extra != nil {
424+
req.Extra["waypoints"] = nil
425+
}
420426

421427
// re-evaluate goal poses to be in the frame of World
422428
// TODO (RSDK-8847) : this is a workaround to help account for us not yet being able to properly synchronize simultaneous motion across

spatialmath/box.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ type box struct {
5656
halfSize [3]float64
5757
boundingSphereR float64
5858
label string
59-
mesh *mesh
59+
mesh *Mesh
6060
rotMatrix *RotationMatrix
6161
once sync.Once
6262
}
@@ -240,14 +240,14 @@ func (b *box) vertices() []r3.Vector {
240240
return verts
241241
}
242242

243-
// vertices returns the vertices defining the box.
244-
func (b *box) toMesh() *mesh {
243+
// toMesh returns a 12-triangle mesh representation of the box, 2 right triangles for each face.
244+
func (b *box) toMesh() *Mesh {
245245
if b.mesh == nil {
246-
m := &mesh{pose: b.pose}
247-
triangles := make([]*triangle, 0, 12)
246+
m := &Mesh{pose: b.pose}
247+
triangles := make([]*Triangle, 0, 12)
248248
verts := b.vertices()
249249
for _, tri := range boxTriangles {
250-
triangles = append(triangles, newTriangle(verts[tri[0]], verts[tri[1]], verts[tri[2]]))
250+
triangles = append(triangles, NewTriangle(verts[tri[0]], verts[tri[1]], verts[tri[2]]))
251251
}
252252
m.triangles = triangles
253253
b.mesh = m

spatialmath/capsule.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ func capsuleVsBoxDistance(c *capsule, other *box) float64 {
261261

262262
// IMPORTANT: meshes are not considered solid. A mesh is not guaranteed to represent an enclosed area. This will measure ONLY the distance
263263
// to the closest triangle in the mesh.
264-
func capsuleVsMeshDistance(c *capsule, other *mesh) float64 {
264+
func capsuleVsMeshDistance(c *capsule, other *Mesh) float64 {
265265
lowDist := math.Inf(1)
266266
for _, t := range other.triangles {
267267
// Measure distance to each mesh triangle
@@ -273,7 +273,7 @@ func capsuleVsMeshDistance(c *capsule, other *mesh) float64 {
273273
return lowDist
274274
}
275275

276-
func capsuleVsTriangleDistance(c *capsule, other *triangle) float64 {
276+
func capsuleVsTriangleDistance(c *capsule, other *Triangle) float64 {
277277
capPt, triPt := closestPointsSegmentTriangle(c.segA, c.segB, other)
278278
return capPt.Sub(triPt).Norm() - c.radius
279279
}

spatialmath/geometry_utils.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func BoundingSphere(geometry Geometry) (Geometry, error) {
100100
}
101101

102102
// closestSegmentTrianglePoints takes a line segment and a triangle, and returns the point on each closest to the other.
103-
func closestPointsSegmentTriangle(ap1, ap2 r3.Vector, t *triangle) (bestSegPt, bestTriPt r3.Vector) {
103+
func closestPointsSegmentTriangle(ap1, ap2 r3.Vector, t *Triangle) (bestSegPt, bestTriPt r3.Vector) {
104104
// The closest triangle point is either on the edge or within the triangle.
105105

106106
// First, handle the case where the closest triangle point is inside the
@@ -109,7 +109,7 @@ func closestPointsSegmentTriangle(ap1, ap2 r3.Vector, t *triangle) (bestSegPt, b
109109
// If the line overlaps the triangle and is parallel to the triangle plane,
110110
// the chosen triangle point is arbitrary.
111111
segPt, _ := closestPointsSegmentPlane(ap1, ap2, t.p0, t.normal)
112-
triPt, inside := t.closestInsidePoint(segPt)
112+
triPt, inside := ClosestTriangleInsidePoint(t, segPt)
113113
if inside {
114114
// If inside is false, then these will not be the best points, because they are based on the segment-plane intersection
115115
return segPt, triPt

spatialmath/mesh.go

Lines changed: 21 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,38 @@
11
package spatialmath
22

3-
import (
4-
"github.com/golang/geo/r3"
5-
)
6-
73
// This file incorporates work covered by the Brax project -- https://github.com/google/brax/blob/main/LICENSE.
84
// Copyright 2021 The Brax Authors, which is licensed under the Apache License Version 2.0 (the “License”).
95
// You may obtain a copy of the license at http://www.apache.org/licenses/LICENSE-2.0.
106

11-
// mesh is a collision geometry that represents a set of triangles that represent a mesh.
12-
type mesh struct {
7+
// Mesh is a set of triangles at some pose. Triangle points are in the frame of the mesh.
8+
type Mesh struct {
139
pose Pose
14-
triangles []*triangle
10+
triangles []*Triangle
1511
}
1612

17-
type triangle struct {
18-
p0 r3.Vector
19-
p1 r3.Vector
20-
p2 r3.Vector
21-
22-
normal r3.Vector
23-
}
24-
25-
func newTriangle(p0, p1, p2 r3.Vector) *triangle {
26-
return &triangle{
27-
p0: p0,
28-
p1: p1,
29-
p2: p2,
30-
normal: PlaneNormal(p0, p1, p2),
13+
// NewMesh creates a mesh from the given triangles and pose.
14+
func NewMesh(pose Pose, triangles []*Triangle) *Mesh {
15+
return &Mesh{
16+
pose: pose,
17+
triangles: triangles,
3118
}
3219
}
3320

34-
// closestPointToCoplanarPoint takes a point, and returns the closest point on the triangle to the given point
35-
// The given point *MUST* be coplanar with the triangle. If it is known ahead of time that the point is coplanar, this is faster.
36-
func (t *triangle) closestPointToCoplanarPoint(pt r3.Vector) r3.Vector {
37-
// Determine whether point is inside all triangle edges:
38-
c0 := pt.Sub(t.p0).Cross(t.p1.Sub(t.p0))
39-
c1 := pt.Sub(t.p1).Cross(t.p2.Sub(t.p1))
40-
c2 := pt.Sub(t.p2).Cross(t.p0.Sub(t.p2))
41-
inside := c0.Dot(t.normal) <= 0 && c1.Dot(t.normal) <= 0 && c2.Dot(t.normal) <= 0
42-
43-
if inside {
44-
return pt
45-
}
46-
47-
// Edge 1:
48-
refPt := ClosestPointSegmentPoint(t.p0, t.p1, pt)
49-
bestDist := pt.Sub(refPt).Norm2()
50-
51-
// Edge 2:
52-
point2 := ClosestPointSegmentPoint(t.p1, t.p2, pt)
53-
if distsq := pt.Sub(point2).Norm2(); distsq < bestDist {
54-
refPt = point2
55-
bestDist = distsq
56-
}
57-
58-
// Edge 3:
59-
point3 := ClosestPointSegmentPoint(t.p2, t.p0, pt)
60-
if distsq := pt.Sub(point3).Norm2(); distsq < bestDist {
61-
return point3
62-
}
63-
return refPt
21+
// Pose returns the pose of the mesh.
22+
func (m *Mesh) Pose() Pose {
23+
return m.pose
6424
}
6525

66-
// closestPointToPoint takes a point, and returns the closest point on the triangle to the given point, as well as whether the point
67-
// is on the edge of the triangle.
68-
// This is slower than closestPointToCoplanarPoint.
69-
func (t *triangle) closestPointToPoint(point r3.Vector) r3.Vector {
70-
closestPtInside, inside := t.closestInsidePoint(point)
71-
if inside {
72-
return closestPtInside
73-
}
74-
75-
// If the closest point is outside the triangle, it must be on an edge, so we
76-
// check each triangle edge for a closest point to the point pt.
77-
closestPt := ClosestPointSegmentPoint(t.p0, t.p1, point)
78-
bestDist := point.Sub(closestPt).Norm2()
79-
80-
newPt := ClosestPointSegmentPoint(t.p1, t.p2, point)
81-
if newDist := point.Sub(newPt).Norm2(); newDist < bestDist {
82-
closestPt = newPt
83-
bestDist = newDist
84-
}
85-
86-
newPt = ClosestPointSegmentPoint(t.p2, t.p0, point)
87-
if newDist := point.Sub(newPt).Norm2(); newDist < bestDist {
88-
return newPt
89-
}
90-
return closestPt
26+
// Triangles returns the triangles associated with the mesh.
27+
func (m *Mesh) Triangles() []*Triangle {
28+
return m.triangles
9129
}
9230

93-
// closestInsidePoint returns the closest point on a triangle IF AND ONLY IF the query point's projection overlaps the triangle.
94-
// Otherwise it will return the query point.
95-
// To visualize this- if one draws a tetrahedron using the triangle and the query point, all angles from the triangle to the query point
96-
// must be <= 90 degrees.
97-
func (t *triangle) closestInsidePoint(point r3.Vector) (r3.Vector, bool) {
98-
// Parametrize the triangle s.t. a point inside the triangle is
99-
// Q = p0 + u * e0 + v * e1, when 0 <= u <= 1, 0 <= v <= 1, and
100-
// 0 <= u + v <= 1. Let e0 = (p1 - p0) and e1 = (p2 - p0).
101-
// We analytically minimize the distance between the point pt and Q.
102-
e0 := t.p1.Sub(t.p0)
103-
e1 := t.p2.Sub(t.p0)
104-
a := e0.Norm2()
105-
b := e0.Dot(e1)
106-
c := e1.Norm2()
107-
d := point.Sub(t.p0)
108-
// The determinant is 0 only if the angle between e1 and e0 is 0
109-
// (i.e. the triangle has overlapping lines).
110-
det := (a*c - b*b)
111-
u := (c*e0.Dot(d) - b*e1.Dot(d)) / det
112-
v := (-b*e0.Dot(d) + a*e1.Dot(d)) / det
113-
inside := (0 <= u) && (u <= 1) && (0 <= v) && (v <= 1) && (u+v <= 1)
114-
return t.p0.Add(e0.Mul(u)).Add(e1.Mul(v)), inside
31+
// Transform transforms the mesh. As triangles are in the mesh's frame, they are unchanged.
32+
func (m *Mesh) Transform(pose Pose) *Mesh {
33+
// Triangle points are in frame of mesh, like the corners of a box, so no need to transform them
34+
return &Mesh{
35+
pose: Compose(pose, m.pose),
36+
triangles: m.triangles,
37+
}
11538
}

spatialmath/mesh_test.go

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)