Example #1
0
 def setUp(self):
     self.spline = CatmullRom([
         Vector3(0, 0, 0),
         Vector3(1, 0, 0),
         Vector3(2, 0, 0),
         Vector3(3, 0, 0)
     ])
Example #2
0
class TestParameterByDistance(unittest.TestCase):
    def setUp(self):
        self.spline = CatmullRom([
            Vector3(0, 0, 0),
            Vector3(1, 0, 0),
            Vector3(2, 0, 0),
            Vector3(3, 0, 0)
        ])

    def testStandardInput(self):
        '''Calculate parameter u that is s meters ahead of u1 (within 7 decimal places'''
        seg = 0
        u1 = 0.25
        s = 0.25  # meters
        uLive = self.spline.findParameterByDistance(seg, u1, s)
        # test with 300 newton iterations
        uAccurate = self.spline.findParameterByDistance(seg, u1, s, 300)
        self.assertAlmostEqual(uLive, uAccurate, 7)

    def testSTooLong(self):
        '''Test when s (in meters) extends beyond the segment'''
        seg = 0
        u1 = 0.5
        s = 0.75
        u = self.spline.findParameterByDistance(seg, u1, s)
        self.assertEqual(1.0, u)

    def testSNegative(self):
        '''Test when s is negative (doesn't make sense for this algorithm)'''
        seg = 0
        u1 = 0.5
        s = -0.75
        u = self.spline.findParameterByDistance(seg, u1, s)
        self.assertEqual(u1, u)
Example #3
0
    def testAgainstBruteForceIntegration(self):
        '''Make sure our numerical integrator is doing a decent job at estimating arc length'''

        self.spline = CatmullRom([
            Vector3(0, 0, 0),
            Vector3(1, 5, 0),
            Vector3(2, -3, 0),
            Vector3(3, 0, 0)
        ])
        seg = 0
        u1 = 0
        u2 = 1
        gaussResult = self.spline.arcLength(seg, u1, u2)

        # Let's use Brute Force
        n = 1000
        dt = 1.0 / n
        L = 0
        t = 0
        for i in range(0, n):
            L += self.spline.velocity(seg, t).length() * dt
            t += dt

        # within a millimeter
        self.assertAlmostEqual(gaussResult, L, 3)
Example #4
0
 def setUp(self):
     # 1 meter long spline
     self.spline = CatmullRom([
         Vector3(0, 0, 0),
         Vector3(1, 0, 0),
         Vector3(2, 0, 0),
         Vector3(3, 0, 0)
     ])
Example #5
0
class TestArclengthToNonDimensional(unittest.TestCase):
    def setUp(self):
        # 1 meter long spline
        self.spline = CatmullRom([
            Vector3(0, 0, 0),
            Vector3(1, 0, 0),
            Vector3(2, 0, 0),
            Vector3(3, 0, 0)
        ])

    def testPTooBig(self):
        '''Test when p is not between 0-1'''
        p = 1.5  # should default to 1.0
        dp = .1  # should be .1 m/s
        seg, u, v = self.spline.arclengthToNonDimensional(p, dp)
        self.assertEqual(seg, 0)
        self.assertEqual(u, 1.0)
        self.assertAlmostEqual(v, 0.1)

    def testPTooSmall(self):
        '''Test when p is not between 0-1'''
        p = -1.5  # should default to 0
        dp = .1  # should be .1 m/s
        seg, u, v = self.spline.arclengthToNonDimensional(p, dp)
        self.assertEqual(seg, 0)
        self.assertEqual(u, 0)
        self.assertAlmostEqual(v, 0.1)

    def testConversionFromArclength(self):
        '''Test conversion in an ideal 1 meter spline'''
        p = 0.5  # should be halfway through spline distance (0.5 meters)
        dp = .1  # should be .1 m/s
        seg, u, v = self.spline.arclengthToNonDimensional(p, dp)
        self.assertEqual(seg, 0)
        self.assertEqual(u, 0.5)
        self.assertAlmostEqual(v, 0.1)

    def testConversionFromArclength2Seg(self):
        '''Test conversion on a 2 segment spline'''
        self.spline = CatmullRom([
            Vector3(0, 0, 0),
            Vector3(1, 0, 0),
            Vector3(2, 0, 0),
            Vector3(4, 0, 0),
            Vector3(5, 0, 0)
        ])
        # x x-x--x x
        p = 0.5  # should be halfway through spline distance (1.5 meters)
        dp = .1
        seg, u, v = self.spline.arclengthToNonDimensional(p, dp)
        self.assertEqual(seg, 1)
        self.assertAlmostEqual(u, 0.27272727)
        self.assertAlmostEqual(v, 0.3)
Example #6
0
 def testConversionFromNonDimensional2Seg(self):
     '''Test conversion on a 2 segment spline'''
     self.spline = CatmullRom([
         Vector3(0, 0, 0),
         Vector3(1, 0, 0),
         Vector3(2, 0, 0),
         Vector3(4, 0, 0),
         Vector3(5, 0, 0)
     ])
     # x x-x--x x
     seg = 1
     u = .5
     v = 2
     p, dp = self.spline.nonDimensionalToArclength(seg, u, v)
     self.assertAlmostEqual(p, 0.66666666)
     self.assertAlmostEqual(dp, 0.66666666)
Example #7
0
 def testConversionFromArclength2Seg(self):
     '''Test conversion on a 2 segment spline'''
     self.spline = CatmullRom([
         Vector3(0, 0, 0),
         Vector3(1, 0, 0),
         Vector3(2, 0, 0),
         Vector3(4, 0, 0),
         Vector3(5, 0, 0)
     ])
     # x x-x--x x
     p = 0.5  # should be halfway through spline distance (1.5 meters)
     dp = .1
     seg, u, v = self.spline.arclengthToNonDimensional(p, dp)
     self.assertEqual(seg, 1)
     self.assertAlmostEqual(u, 0.27272727)
     self.assertAlmostEqual(v, 0.3)
Example #8
0
    def generateSplines(self, waypoints):
        '''Generate the multi-point spline'''

        # shortest distance between each angle
        for i in range(0, len(waypoints) - 1):
            # calculate difference of yaw and next yaw
            delta = self.waypoints[i + 1].yaw - self.waypoints[i].yaw

            # don't take the long way around
            if abs(delta) > 180:
                # add 360 to all following yaws (CW)
                if delta < 0.0:
                    direction = 1
                # or remove 360 from all following yaws (CCW)
                else:
                    direction = -1

                # list tracking directions
                self.yawDirections.append(direction)

                # update all following yaws
                for j in range(i + 1, len(self.waypoints)):
                    self.waypoints[
                        j].yaw = self.waypoints[j].yaw + direction * 360

        # generate camera spline
        camPts = [Vector2(x.pitch, x.yaw) for x in self.waypoints]

        # Generate artificial end points for spline
        # extend slope of first two points to generate initial control point
        endPt1 = camPts[0] + (camPts[0] - camPts[1])
        # extend slope of last two points to generate final control point
        endPt2 = camPts[-1] + (camPts[-1] - camPts[-2])

        # append virtual control points to cartesian list
        camPts = [endPt1] + camPts + [endPt2]

        # Build spline object
        try:
            self.camSpline = CatmullRom(camPts)
        except ValueError, e:
            logger.log("%s", e)
            self.shotMgr.enterShot(shots.APP_SHOT_NONE)  # exit the shot
            return False
Example #9
0
    def __init__(self, points, maxSpeed, minSpeed, tanAccelLim, normAccelLim,
                 smoothStopP, maxAlt):
        # Maximum tangential acceleration along the cable, m/s^2
        self.tanAccelLim = tanAccelLim

        # Maximum acceleration normal to the cable, m/s^2
        self.normAccelLim = normAccelLim

        # Smoothness of stops at the endpoints and at targets along the cable
        self.smoothStopP = smoothStopP

        # Maximum speed along the cable, m/s
        self.maxSpeed = maxSpeed

        # Minimum speed along the cable, m/s
        self.minSpeed = minSpeed

        # Minimum allowable position.z, meters (AKA max altitude), Convert Altitude (NEU) to NED
        if maxAlt is not None:
            self.posZLimit = -maxAlt
        else:
            self.posZLimit = None

        # Input speed
        self.desiredSpeed = 0.

        # Current speed along the cable, m/s
        self.speed = 0.

        # Catmull-Rom spline with added virtual tangency control points at either end
        self.spline = CatmullRom([points[0] * 2 - points[1]] + points +
                                 [points[-1] * 2 - points[-2]])

        # Number of spline segments (should really come from CatmullRom)
        self.numSegments = len(points) - 1

        # Current position in P domain, parameter normalized to cable total arc length
        self.currentP = 1.0

        # Target position in P domain
        self.targetP = self.currentP

        # Previously reached target, once set
        self.prevReachedTarget = None

        # Current segment, ranges from 0 to # of segments-1
        self.currentSeg, self.currentU = self.spline.arclengthToNonDimensional(
            self.currentP)

        # Current position as a Vector3, meters
        self.position = self.spline.position(self.currentSeg, self.currentU)

        # Current velocity as a Vector3, m/s
        self.velocity = Vector3()

        # Flag to indicate that the maximum altitude has been exceeded
        self.maxAltExceeded = False

        # Number of segments in curvature map
        self.curvatureMapNumSegments = int(
            math.ceil(self.spline.totalArcLength / CURVATURE_MAP_RES))

        # Number of joints in curvature map
        self.curvatureMapNumJoints = self.curvatureMapNumSegments + 1

        # Curvature map joint positions in p domain
        self.curvatureMapJointsP, self.curvatureMapSegLengthP = linspace(
            0., 1., self.curvatureMapNumJoints, retstep=True)

        # Curvature map segment length in meters
        self.curvatureMapSegLengthM = self.curvatureMapSegLengthP * self.spline.totalArcLength

        # Non-dimensional curvature map joint position (cache)
        self.curvatureMapJointsNonDimensional = [
            None for _ in range(self.curvatureMapNumJoints)
        ]

        # Speed limits for each curvature map segment (cache)
        self.curvatureMapSpeedLimits = [
            None for _ in range(self.curvatureMapNumSegments)
        ]

        # Thread lock on curvature map segments
        self.curvatureMapLocks = [
            threading.Lock() for _ in range(self.curvatureMapNumSegments)
        ]
        self.curvatureMapSegmentsComputedLock = threading.Lock()

        # number of map segments that have been computed by the curvatureMapThread
        self.curvatureMapSegmentsComputed = 0

        # flag that indicates to the thread to die
        self.poisonPill = False

        # setup a worker thread to compute map segment maximum speeds
        self.curvatureMapThread = threading.Thread(
            target=self._computeCurvatureMap)
        self.curvatureMapThread.setDaemon(True)

        # start the worker thread
        self.curvatureMapThread.start()
Example #10
0
class CableController():
    def __init__(self, points, maxSpeed, minSpeed, tanAccelLim, normAccelLim,
                 smoothStopP, maxAlt):
        # Maximum tangential acceleration along the cable, m/s^2
        self.tanAccelLim = tanAccelLim

        # Maximum acceleration normal to the cable, m/s^2
        self.normAccelLim = normAccelLim

        # Smoothness of stops at the endpoints and at targets along the cable
        self.smoothStopP = smoothStopP

        # Maximum speed along the cable, m/s
        self.maxSpeed = maxSpeed

        # Minimum speed along the cable, m/s
        self.minSpeed = minSpeed

        # Minimum allowable position.z, meters (AKA max altitude), Convert Altitude (NEU) to NED
        if maxAlt is not None:
            self.posZLimit = -maxAlt
        else:
            self.posZLimit = None

        # Input speed
        self.desiredSpeed = 0.

        # Current speed along the cable, m/s
        self.speed = 0.

        # Catmull-Rom spline with added virtual tangency control points at either end
        self.spline = CatmullRom([points[0] * 2 - points[1]] + points +
                                 [points[-1] * 2 - points[-2]])

        # Number of spline segments (should really come from CatmullRom)
        self.numSegments = len(points) - 1

        # Current position in P domain, parameter normalized to cable total arc length
        self.currentP = 1.0

        # Target position in P domain
        self.targetP = self.currentP

        # Previously reached target, once set
        self.prevReachedTarget = None

        # Current segment, ranges from 0 to # of segments-1
        self.currentSeg, self.currentU = self.spline.arclengthToNonDimensional(
            self.currentP)

        # Current position as a Vector3, meters
        self.position = self.spline.position(self.currentSeg, self.currentU)

        # Current velocity as a Vector3, m/s
        self.velocity = Vector3()

        # Flag to indicate that the maximum altitude has been exceeded
        self.maxAltExceeded = False

        # Number of segments in curvature map
        self.curvatureMapNumSegments = int(
            math.ceil(self.spline.totalArcLength / CURVATURE_MAP_RES))

        # Number of joints in curvature map
        self.curvatureMapNumJoints = self.curvatureMapNumSegments + 1

        # Curvature map joint positions in p domain
        self.curvatureMapJointsP, self.curvatureMapSegLengthP = linspace(
            0., 1., self.curvatureMapNumJoints, retstep=True)

        # Curvature map segment length in meters
        self.curvatureMapSegLengthM = self.curvatureMapSegLengthP * self.spline.totalArcLength

        # Non-dimensional curvature map joint position (cache)
        self.curvatureMapJointsNonDimensional = [
            None for _ in range(self.curvatureMapNumJoints)
        ]

        # Speed limits for each curvature map segment (cache)
        self.curvatureMapSpeedLimits = [
            None for _ in range(self.curvatureMapNumSegments)
        ]

        # Thread lock on curvature map segments
        self.curvatureMapLocks = [
            threading.Lock() for _ in range(self.curvatureMapNumSegments)
        ]
        self.curvatureMapSegmentsComputedLock = threading.Lock()

        # number of map segments that have been computed by the curvatureMapThread
        self.curvatureMapSegmentsComputed = 0

        # flag that indicates to the thread to die
        self.poisonPill = False

        # setup a worker thread to compute map segment maximum speeds
        self.curvatureMapThread = threading.Thread(
            target=self._computeCurvatureMap)
        self.curvatureMapThread.setDaemon(True)

        # start the worker thread
        self.curvatureMapThread.start()

    def __del__(self):
        self.poisonPill = True
        self.curvatureMapThread.join(timeout=2)

    # Public interface:

    def reachedTarget(self):
        '''Return True if we've reached the target, else False'''

        return abs(self.currentP - self.targetP
                   ) * self.spline.totalArcLength < TARGET_EPSILON_M

    def setTargetP(self, targetP):
        '''Interface to set a target P'''

        self.targetP = targetP

    def trackSpeed(self, speed):
        '''Updates controller desired speed'''

        self.desiredSpeed = speed

    def update(self, dt):
        '''Advances controller along cable by dt'''

        # Speed always in direction of target
        self.desiredSpeed = math.copysign(self.desiredSpeed,
                                          self.targetP - self.currentP)
        self.speed = constrain(self._constrainSpeed(self.desiredSpeed),
                               self.speed - self.tanAccelLim * dt,
                               self.speed + self.tanAccelLim * dt)
        self._traverse(dt)

    def setCurrentP(self, p):
        '''Sets the controller's current P position on the cable'''

        self.currentP = p
        self.currentSeg, self.currentU = self.spline.arclengthToNonDimensional(
            self.currentP)

    def killCurvatureMapThread(self):
        '''Sets poisonPill to True so the curvatureMapThread knows to die'''

        self.poisonPill = True

    # Internal functions:
    def _computeCurvatureMap(self):
        '''Computes curvature map, prioritizes map construction based on vehicle position and direction of motion'''

        while True:
            searchStart = self._getCurvatureMapSegment(self.currentP)

            if self.speed > 0:
                # Search ahead, then behind
                for i in range(searchStart,
                               self.curvatureMapNumSegments) + list(
                                   reversed(range(0, searchStart))):
                    if self._computeCurvatureMapSpeedLimit(i):
                        break
            elif self.speed < 0:
                # Search behind, then ahead
                for i in list(reversed(range(0, searchStart + 1))) + range(
                        searchStart + 1, self.curvatureMapNumSegments):
                    if self._computeCurvatureMapSpeedLimit(i):
                        break
            else:  # speed == 0
                # Search alternately ahead and behind
                searchList = [
                    x for t in list(
                        itertools.izip_longest(
                            range(searchStart, self.curvatureMapNumSegments),
                            reversed(range(0, searchStart)))) for x in t
                    if x is not None
                ]
                for i in searchList:
                    if self._computeCurvatureMapSpeedLimit(i):
                        break
            # if all map segments have been computed then quit the thread
            with self.curvatureMapSegmentsComputedLock:
                if self.curvatureMapSegmentsComputed == self.curvatureMapNumSegments:
                    self.poisonPill = True

            if self.poisonPill:
                break

    def _computeCurvatureMapSpeedLimit(self, mapSeg):
        '''Computes speed limit for the requested map segment'''

        with self.curvatureMapLocks[mapSeg]:

            # if the speed limit has already been computed for this map segment, then don't do any work
            if self.curvatureMapSpeedLimits[mapSeg] is not None:
                return False

            # if non-dimensional parameter has not yet been created for the associated left joint, then create it
            if self.curvatureMapJointsNonDimensional[mapSeg] is None:
                self.curvatureMapJointsNonDimensional[
                    mapSeg] = self.spline.arclengthToNonDimensional(
                        self.curvatureMapJointsP[mapSeg])

            # if non-dimensional parameter has not yet been created for the associated right joint, then create it
            if self.curvatureMapJointsNonDimensional[mapSeg + 1] is None:
                self.curvatureMapJointsNonDimensional[
                    mapSeg + 1] = self.spline.arclengthToNonDimensional(
                        self.curvatureMapJointsP[mapSeg + 1])

            # split returned non-dimensional parameter tuple (seg,u) into separate values
            seg1, u1 = self.curvatureMapJointsNonDimensional[mapSeg]
            seg2, u2 = self.curvatureMapJointsNonDimensional[mapSeg + 1]

            # returns arc length for current spline segment, or the larger of the two segments if our map segment spans across multiple spline segments
            maxSegLen = max(self.spline.arcLengths[seg1:seg2 + 1])  # m

            # run a golden section search to find the segment,u pair for the point of maximum curvature in the requested map segment
            # (segment,u) are stored as segment+u, e.g. segment 1, u = 0.25 -> 1.25
            maxCurvatureSegU = goldenSection(
                lambda x: -self.spline.curvature(int(x), x - int(x)),
                seg1 + u1,
                seg2 + u2,
                tol=1e-1 / maxSegLen)

            # run a golden section search to find the segment,u pair for the point of minimum Z (aka max altitude)
            minPosZSegU = goldenSection(
                lambda x: self.spline.position(int(x), x - int(x)).z,
                seg1 + u1,
                seg2 + u2,
                tol=1e-1 / maxSegLen)

            # split segment+u into segment,u and evaluate curvature at this point
            maxCurvature = self.spline.curvature(
                int(maxCurvatureSegU),
                maxCurvatureSegU - int(maxCurvatureSegU))

            #split segment+u into segment,u and evalute position.z at this point
            minPosZ = self.spline.position(int(minPosZSegU), minPosZSegU -
                                           int(minPosZSegU)).z  #m

            # this prevents the copter from traversing segments of the cable
            # that are above its altitude limit
            if self.posZLimit is not None and minPosZ < self.posZLimit:
                self.maxAltExceeded = True
                #this cable will breach the altitude limit, make the speed limit for this segment 0 to stop the vehicle
                self.curvatureMapSpeedLimits[mapSeg] = 0.
            else:
                if maxCurvature != 0.:
                    # limit maxspeed by the max allowable normal acceleration at that point, bounded on the lower end by minSpeed
                    self.curvatureMapSpeedLimits[mapSeg] = max(
                        math.sqrt(self.normAccelLim / maxCurvature),
                        self.minSpeed)
                else:
                    # if curvature is zero, means a straight segment
                    self.curvatureMapSpeedLimits[mapSeg] = self.maxSpeed

            with self.curvatureMapSegmentsComputedLock:
                self.curvatureMapSegmentsComputed += 1

            return True

    def _getCurvatureMapSpeedLimit(self, mapSeg):
        '''Look up the speed limit for the requested map segment'''

        # sanitize mapSeg
        if mapSeg < 0 or mapSeg >= self.curvatureMapNumSegments:
            return 0.

        self._computeCurvatureMapSpeedLimit(mapSeg)

        return self.curvatureMapSpeedLimits[mapSeg]

    def _traverse(self, dt):
        ''' Advances the controller along the spline '''

        spline_vel_unit = self.spline.velocity(self.currentSeg, self.currentU)
        spline_vel_norm = spline_vel_unit.normalize()

        # advances u by the amount specified by our speed and dt
        self.currentU += self.speed * dt / spline_vel_norm

        # handle traversing spline segments
        if self.currentU > 1.:
            if self.currentSeg < self.numSegments - 1:
                self.currentSeg += 1
                self.currentU = 0.  # NOTE: this truncates steps which cross spline joints
            else:
                self.currentU = 1.
        elif self.currentU < 0.:
            if self.currentSeg > 0:
                self.currentSeg -= 1
                self.currentU = 1.  # NOTE: this truncates steps which cross spline joints
            else:
                self.currentU = 0.

        # calculate our currentP
        self.currentP = self.spline.nonDimensionalToArclength(
            self.currentSeg, self.currentU)[0]

        # calculate our position and velocity commands
        self.position = self.spline.position(self.currentSeg, self.currentU)
        self.velocity = spline_vel_unit * self.speed

    def _constrainSpeed(self, speed):
        '''Looks ahead and behind current controller position and constrains to a speed limit'''

        if speed > 0:
            return min(self.maxSpeed, speed,
                       self._getPosSpeedLimit(self.currentP))
        elif speed < 0:
            return max(-self.maxSpeed, speed,
                       self._getNegSpeedLimit(self.currentP))

        return speed

    def _speedCurve(self, dist, speed):
        '''Returns speed based on the sqrt function or a linear ramp (depending on dist)'''

        linear_velocity = self.tanAccelLim / self.smoothStopP
        linear_dist = linear_velocity / self.smoothStopP

        if speed > linear_velocity:
            return math.sqrt(2. * self.tanAccelLim *
                             (speed**2 / (2. * self.tanAccelLim) + dist))
        else:
            p1 = speed / self.smoothStopP
            p2 = p1 + dist

            if p2 > linear_dist:
                return math.sqrt(2. * self.tanAccelLim *
                                 (p2 - 0.5 * linear_dist))
            else:
                return p2 * self.smoothStopP

    def _maxLookAheadDist(self):
        '''Calculate how far it would take to come to a complete stop '''

        linear_velocity = self.tanAccelLim / self.smoothStopP
        linear_dist = linear_velocity / self.smoothStopP

        if abs(self.speed) > linear_velocity:
            return 0.5 * abs(
                self.speed)**2 / self.tanAccelLim + 0.5 * linear_dist
        else:
            return abs(self.speed) / self.smoothStopP

    def _getCurvatureMapSegment(self, p):
        '''Get the curvature map segment index at the location p'''

        return int(
            min(math.floor(p / self.curvatureMapSegLengthP),
                self.curvatureMapNumSegments - 1))

    def _getDistToCurvatureMapSegmentBegin(self, p1, idx):
        '''Get distance from p1 to the beginning of the idx curvature map segment in meters'''

        p2 = self.curvatureMapJointsP[idx]
        return abs(p1 - p2) * self.spline.totalArcLength

    def _getDistToCurvatureMapSegmentEnd(self, p1, idx):
        '''Get distance from p1 to the end of the idx curvature map segment in meters'''

        p2 = self.curvatureMapJointsP[idx + 1]
        return abs(p1 - p2) * self.spline.totalArcLength

    def _getPosSpeedLimit(self, p):
        '''Returns speed limit for a requested arc length normalized parameter, p, moving in the positive direction'''

        # Identify our current curvature map segment
        mapSeg = self._getCurvatureMapSegment(p)

        # get speed limit for the upcoming curvature map segment
        nextMapSegSpeed = self._getCurvatureMapSpeedLimit(mapSeg + 1)

        # get distance (in meters) from current position to start of next curvature map segment
        nextMapSegDist = self._getDistToCurvatureMapSegmentEnd(p, mapSeg)

        # set speed limit to the minimum of the current curvature map segment and the transition to the next curvature map segment speed
        speedLimit = min(self._getCurvatureMapSpeedLimit(mapSeg),
                         self._speedCurve(nextMapSegDist,
                                          nextMapSegSpeed))  # m/s

        # loop through all remaining segments in that direction
        for mapSeg in range(mapSeg + 1, self.curvatureMapNumSegments):
            # increment distance by another curvature map segment length
            nextMapSegDist += self.curvatureMapSegLengthM

            # if that distance is greater than the distance it would take to stop, then break to save time (no need to look ahead any further)
            if nextMapSegDist > self._maxLookAheadDist():
                break

            # get curvature map seg speed at this next segment
            nextMapSegSpeed = self._getCurvatureMapSpeedLimit(
                mapSeg + 1
            )  # NOTE: self.getCurvatureMapSpeedLimit(self.curvatureMapNumSegments) is 0

            # limit us if the new map segment speed is slower than our current speed limit
            speedLimit = min(speedLimit,
                             self._speedCurve(nextMapSegDist, nextMapSegSpeed))

        # if targetP is ahead of currentP then check for a speed limit to slow down at the target
        if self.targetP >= self.currentP:
            speedLimit = min(
                speedLimit,
                self._speedCurve(
                    abs(self.targetP - self.currentP) *
                    self.spline.totalArcLength, 0))

        return speedLimit

    def _getNegSpeedLimit(self, p):
        '''Returns speed limit for a requested arc length normalized parameter, p, moving in the negative direction'''

        # Identify our current curvature map segment
        mapSeg = self._getCurvatureMapSegment(p)

        # get speed limit for the previous curvature map segment
        prevMapSegSpeed = self._getCurvatureMapSpeedLimit(mapSeg - 1)

        # get distance (in meters) from current position to start of previous curvature map segment
        prevMapSegDist = self._getDistToCurvatureMapSegmentBegin(p, mapSeg)

        # set speed limit to the minimum of the current curvature map segment and the transition to the previous curvature map segment speed
        speedLimit = min(self._getCurvatureMapSpeedLimit(mapSeg),
                         self._speedCurve(prevMapSegDist,
                                          prevMapSegSpeed))  # m/s

        # loop through all remaining segments in that direction
        for mapSeg in reversed(range(0, mapSeg)):
            # increment distance by another curvature map segment length
            prevMapSegDist += self.curvatureMapSegLengthM

            # if that distance is greater than the distance it would take to stop, then break to save time (no need to look ahead any further)
            if prevMapSegDist > self._maxLookAheadDist():
                break

            # get curvature map seg speed at this previous segment
            prevMapSegSpeed = self._getCurvatureMapSpeedLimit(
                mapSeg - 1)  # NOTE: self.getCurvatureMapSpeedLimit(-1) is 0

            # limit us if the new map segment speed is slower than our current speed limit
            speedLimit = min(speedLimit,
                             self._speedCurve(prevMapSegDist, prevMapSegSpeed))

        if self.targetP <= self.currentP:
            speedLimit = min(
                speedLimit,
                self._speedCurve(
                    abs(self.targetP - self.currentP) *
                    self.spline.totalArcLength, 0))

        return -speedLimit
Example #11
0
class TestArcLength(unittest.TestCase):
    def setUp(self):
        self.spline = CatmullRom([
            Vector3(0, 0, 0),
            Vector3(1, 0, 0),
            Vector3(2, 0, 0),
            Vector3(3, 0, 0)
        ])

    def testStandardInput(self):
        '''Calculate arc length of the segment and verify that it's close to 1 (within 7 decimal places)'''
        seg = 0
        u1 = 0
        u2 = 1
        length = self.spline.arcLength(seg, u1, u2)
        self.assertAlmostEqual(1, length, 7)

    def testU2LessU1(self):
        '''Tests case where u2 < u1'''
        seg = 0
        u2 = 0.5
        u1 = 0.75
        length = self.spline.arcLength(seg, u1, u2)
        self.assertEqual(0, length)

    def testU1OutOfBounds(self):
        '''Test when u1 is not between 0-1'''
        seg = 0
        u1 = -0.2
        u2 = 0.5
        length = self.spline.arcLength(seg, u1, u2)
        self.assertAlmostEqual(0.5, length)

    def testU2OutOfBounds(self):
        '''Test when u2 is not between 0-1'''
        seg = 0
        u1 = 0.5
        u2 = 1.5
        length = self.spline.arcLength(seg, u1, u2)
        self.assertAlmostEqual(0.5, length)

    def testU1andU2OutOfBounds(self):
        '''Test when u1 and u2 are not between 0-1'''
        seg = 0
        u1 = 1.1
        u2 = 1.2
        length = self.spline.arcLength(seg, u1, u2)
        self.assertAlmostEqual(0, length)

        u1 = -0.7
        u2 = -0.5
        length = self.spline.arcLength(seg, u1, u2)
        self.assertAlmostEqual(0, length)

    def testAgainstBruteForceIntegration(self):
        '''Make sure our numerical integrator is doing a decent job at estimating arc length'''

        self.spline = CatmullRom([
            Vector3(0, 0, 0),
            Vector3(1, 5, 0),
            Vector3(2, -3, 0),
            Vector3(3, 0, 0)
        ])
        seg = 0
        u1 = 0
        u2 = 1
        gaussResult = self.spline.arcLength(seg, u1, u2)

        # Let's use Brute Force
        n = 1000
        dt = 1.0 / n
        L = 0
        t = 0
        for i in range(0, n):
            L += self.spline.velocity(seg, t).length() * dt
            t += dt

        # within a millimeter
        self.assertAlmostEqual(gaussResult, L, 3)
Example #12
0
class TestCatmullRom(unittest.TestCase):
    def setUp(self):
        self.spline = CatmullRom([
            Vector3(0, 0, 0),
            Vector3(1, 0, 0),
            Vector3(2, 0, 0),
            Vector3(3, 0, 0)
        ])

    def testUpdateSplineCoefficientsError(self):
        '''Tests that an error is raised if we call updateSplineCoefficients with an out of range segment'''
        self.assertRaises(ValueError, self.spline.updateSplineCoefficients, 3)

    def testPosition(self):
        '''For an evenly spaced straight-line spline, test that position is traversed'''
        seg = 0
        u = 0
        q = self.spline.position(seg, u)
        self.assertEqual(q, Vector3(1.0, 0.0, 0.0))
        u = 0.5
        q = self.spline.position(seg, u)
        self.assertEqual(q, Vector3(1.5, 0.0, 0.0))
        u = 1.0
        q = self.spline.position(seg, u)
        self.assertEqual(q, Vector3(2.0, 0.0, 0.0))

    def testVelocity(self):
        '''For an evenly spaced straight-line spline, test that velocity is constant'''
        seg = 0
        u = 0
        dq = self.spline.velocity(seg, u)
        self.assertEqual(dq, Vector3(1.0, 0.0, 0.0))
        u = 0.5
        dq = self.spline.velocity(seg, u)
        self.assertEqual(dq, Vector3(1.0, 0.0, 0.0))
        u = 1.0
        dq = self.spline.velocity(seg, u)
        self.assertEqual(dq, Vector3(1.0, 0.0, 0.0))

    def testAcceleration(self):
        '''For an evenly spaced straight-line spline, test that acceleration is zero'''
        seg = 0
        u = 0
        ddq = self.spline.acceleration(seg, u)
        self.assertEqual(ddq, Vector3(0.0, 0.0, 0.0))
        u = 0.5
        ddq = self.spline.acceleration(seg, u)
        self.assertEqual(ddq, Vector3(0.0, 0.0, 0.0))
        u = 1.0
        ddq = self.spline.acceleration(seg, u)
        self.assertEqual(ddq, Vector3(0.0, 0.0, 0.0))

    def testCurvature(self):
        '''For an evenly spaced straight-line spline, test that curvature is zero'''
        seg = 0
        u = 0
        curv = self.spline.curvature(seg, u)
        self.assertEqual(curv, 0.0)
        u = 0.5
        curv = self.spline.curvature(seg, u)
        self.assertEqual(curv, 0.0)
        u = 1.0
        curv = self.spline.curvature(seg, u)
        self.assertEqual(curv, 0.0)
Example #13
0
class TestNonDimensionalToArclength(unittest.TestCase):
    def setUp(self):
        # 1 meter long spline
        self.spline = CatmullRom([
            Vector3(0, 0, 0),
            Vector3(1, 0, 0),
            Vector3(2, 0, 0),
            Vector3(3, 0, 0)
        ])

    def testSegTooBig(self):
        '''Test when segment doesn't exist (too high)'''
        seg = 1  # for a 4 point spline, we only have 1 segment with index 0 x  x----x   x
        u = 0.5
        v = 1
        p, dp = self.spline.nonDimensionalToArclength(seg, u, v)
        self.assertEqual(p, 0.5)
        self.assertAlmostEqual(dp, 1.0)

    def testSegTooSmall(self):
        '''Test when segment doesn't exist (no negative segments allowed)'''
        seg = - \
            1  # for a 4 point spline, we only have 1 segment with index 0 x  x----x   x
        u = 0.5
        v = 1
        p, dp = self.spline.nonDimensionalToArclength(seg, u, v)
        self.assertEqual(p, 0.5)
        self.assertAlmostEqual(dp, 1.0)

    def testUTooBig(self):
        '''Test when U is not between 0-1'''
        seg = 0
        u = 1.5
        v = 1
        p, dp = self.spline.nonDimensionalToArclength(seg, u, v)
        self.assertEqual(p, 1.0)
        self.assertAlmostEqual(dp, 1.0)

    def testUTooSmall(self):
        '''Test when U is not between 0-1'''
        seg = 0
        u = -1
        v = 1
        p, dp = self.spline.nonDimensionalToArclength(seg, u, v)
        self.assertEqual(p, 0.0)
        self.assertAlmostEqual(dp, 1.0)

    def testConversionFromNonDimensional(self):
        '''Test conversion on an ideal 1 meter spline'''
        seg = 0
        u = .75
        v = 2
        p, dp = self.spline.nonDimensionalToArclength(seg, u, v)
        self.assertEqual(p, 0.75)
        self.assertAlmostEqual(dp, 2.0)

    def testConversionFromNonDimensional2Seg(self):
        '''Test conversion on a 2 segment spline'''
        self.spline = CatmullRom([
            Vector3(0, 0, 0),
            Vector3(1, 0, 0),
            Vector3(2, 0, 0),
            Vector3(4, 0, 0),
            Vector3(5, 0, 0)
        ])
        # x x-x--x x
        seg = 1
        u = .5
        v = 2
        p, dp = self.spline.nonDimensionalToArclength(seg, u, v)
        self.assertAlmostEqual(p, 0.66666666)
        self.assertAlmostEqual(dp, 0.66666666)