Advertisement
Smatic

Bezier Trajectory Bitcraze

Apr 21st, 2022
285
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.27 KB | None | 0 0
  1. # -*- coding: utf-8 -*-
  2. #
  3. #     ||          ____  _ __
  4. #  +------+      / __ )(_) /_______________ _____  ___
  5. #  | 0xBC |     / __  / / __/ ___/ ___/ __ `/_  / / _ \
  6. #  +------+    / /_/ / / /_/ /__/ /  / /_/ / / /_/  __/
  7. #   ||  ||    /_____/_/\__/\___/_/   \__,_/ /___/\___/
  8. #
  9. #  Copyright (C) 2019 Bitcraze AB
  10. #
  11. #  This program is free software; you can redistribute it and/or
  12. #  modify it under the terms of the GNU General Public License
  13. #  as published by the Free Software Foundation; either version 2
  14. #  of the License, or (at your option) any later version.
  15. #
  16. #  This program is distributed in the hope that it will be useful,
  17. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  19. #  GNU General Public License for more details.
  20. # You should have received a copy of the GNU General Public License
  21. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  22. """
  23. Example of how to generate trajectories for the High Level commander using
  24. Bezier curves. The output from this script is intended to be pasted into the
  25. autonomous_sequence_high_level.py example.
  26.  
  27. This code uses Bezier curves of degree 7, that is with 8 control points.
  28. See https://en.wikipedia.org/wiki/B%C3%A9zier_curve
  29.  
  30. All coordinates are (x, y, z, yaw)
  31. """
  32. import math
  33.  
  34. import numpy as np
  35.  
  36.  
  37. class Node:
  38.     """
  39.    A node represents the connection point between two Bezier curves
  40.    (called Segments).
  41.    It holds 4 control points for each curve and the positions of the control
  42.    points are set to join the curves with continuity in c0, c1, c2, c3.
  43.    See https://www.cl.cam.ac.uk/teaching/2000/AGraphHCI/SMEG/node3.html
  44.  
  45.    The control points are named
  46.    p4, p5, p6 and p7 for the tail of the first curve
  47.    q0, q1, q2, q3 for the head of the second curve
  48.    """
  49.  
  50.     def __init__(self, q0, q1=None, q2=None, q3=None):
  51.         """
  52.        Create a Node. Pass in control points to define the shape of the
  53.        two segments that share the Node. The control points are for the
  54.        second segment, that is the four first control points of the Bezier
  55.        curve after the node. The control points for the Bezier curve before
  56.        the node are calculated from the existing control points.
  57.        The control points are for scale = 1, that is if the Bezier curve
  58.        after the node has scale = 1 it will have exactly these handles. If the
  59.        curve after the node has a different scale the handles will be moved
  60.        accordingly when the Segment is created.
  61.  
  62.        q0 is required, the other points are optional.
  63.        if q1 is missing it will be set to generate no velocity in q0.
  64.        If q2 is missing it will be set to generate no acceleration in q0.
  65.        If q3 is missing it will be set to generate no jerk in q0.
  66.  
  67.        If only q0 is set, the node will represent a point where the Crazyflie
  68.        has no velocity. Good for starting and stopping.
  69.  
  70.        To get a fluid motion between segments, q1 must be set.
  71.  
  72.        :param q0: The position of the node
  73.        :param q1: The position of the first control point
  74.        :param q2: The position of the second control point
  75.        :param q3: The position of the third control point
  76.        """
  77.         self._control_points = np.zeros([2, 4, 4])
  78.  
  79.         q0 = np.array(q0)
  80.  
  81.         if q1 is None:
  82.             q1 = q0
  83.         else:
  84.             q1 = np.array(q1)
  85.             # print('q1 generated:', q1)
  86.  
  87.         d = q1 - q0
  88.  
  89.         if q2 is None:
  90.             q2 = q0 + 2 * d
  91.             # print('q2 generated:', q2)
  92.         else:
  93.             q2 = np.array(q2)
  94.  
  95.         e = 3 * q2 - 2 * q0 - 6 * d
  96.  
  97.         if q3 is None:
  98.             q3 = e + 3 * d
  99.             # print('q3 generated:', q3)
  100.         else:
  101.             q3 = np.array(q3)
  102.  
  103.         p7 = q0
  104.         p6 = q1 - 2 * d
  105.         p5 = q2 - 4 * d
  106.         p4 = 2 * e - q3
  107.  
  108.         self._control_points[0][0] = q0
  109.         self._control_points[0][1] = q1
  110.         self._control_points[0][2] = q2
  111.         self._control_points[0][3] = q3
  112.  
  113.         self._control_points[1][3] = p7
  114.         self._control_points[1][2] = p6
  115.         self._control_points[1][1] = p5
  116.         self._control_points[1][0] = p4
  117.  
  118.     def get_head_points(self):
  119.         return self._control_points[0]
  120.  
  121.     def get_tail_points(self):
  122.         return self._control_points[1]
  123.  
  124.     def draw_unscaled_controlpoints(self, visualizer):
  125.         color = (0.8, 0.8, 0.8)
  126.         for p in self._control_points[0]:
  127.             visualizer.marker(p[0:3], color=color)
  128.         for p in self._control_points[1]:
  129.             visualizer.marker(p[0:3], color=color)
  130.  
  131.     def print(self):
  132.         print('Node ---')
  133.         print('Tail:')
  134.         for c in self._control_points[1]:
  135.             print(c)
  136.         print('Head:')
  137.         for c in self._control_points[0]:
  138.             print(c)
  139.  
  140.  
  141. class Segment:
  142.     """
  143.    A Segment represents a Bezier curve of degree 7. It uses two Nodes to
  144.    define the shape. The scaling of the segment will move the handles compared
  145.    to the Node to maintain continuous position, velocity, acceleration and
  146.    jerk through the Node.
  147.    A Segment can generate a polynomial that is compatible with the High Level
  148.    Commander, either in python to be sent to the Crazyflie, or as C code to be
  149.    used in firmware.
  150.    A Segment can also be rendered in Vispy.
  151.    """
  152.  
  153.     def __init__(self, head_node, tail_node, scale):
  154.         self._scale = scale
  155.  
  156.         unscaled_points = np.concatenate(
  157.             [head_node.get_head_points(), tail_node.get_tail_points()])
  158.  
  159.         self._points = self._scale_control_points(unscaled_points, self._scale)
  160.  
  161.         polys = self._convert_to_polys()
  162.         self._polys = self._stretch_polys(polys, self._scale)
  163.  
  164.         self._vel = self._deriv(self._polys)
  165.         self._acc = self._deriv(self._vel)
  166.         self._jerk = self._deriv(self._acc)
  167.  
  168.     def _convert_to_polys(self):
  169.         n = len(self._points) - 1
  170.         result = np.zeros([4, 8])
  171.  
  172.         for d in range(4):
  173.             for j in range(n + 1):
  174.                 s = 0.0
  175.                 for i in range(j + 1):
  176.                     s += ((-1) ** (i + j)) * self._points[i][d] / (
  177.                         math.factorial(i) * math.factorial(j - i))
  178.  
  179.                 c = s * math.factorial(n) / math.factorial(n - j)
  180.                 result[d][j] = c
  181.  
  182.         return result
  183.  
  184.     def draw_trajectory(self, visualizer):
  185.         self._draw(self._polys, 'black', visualizer)
  186.  
  187.     def draw_vel(self, visualizer):
  188.         self._draw(self._vel, 'green', visualizer)
  189.  
  190.     def draw_acc(self, visualizer):
  191.         self._draw(self._acc, 'red', visualizer)
  192.  
  193.     def draw_jerk(self, visualizer):
  194.         self._draw(self._jerk, 'blue', visualizer)
  195.  
  196.     def draw_control_points(self, visualizer):
  197.         for p in self._points:
  198.             visualizer.marker(p[0:3])
  199.  
  200.     def _draw(self, polys, color, visualizer):
  201.         step = self._scale / 32
  202.         prev = None
  203.         for t in np.arange(0.0, self._scale + step, step):
  204.             p = self._eval_xyz(polys, t)
  205.  
  206.             if prev is not None:
  207.                 visualizer.line(p, prev, color=color)
  208.  
  209.             prev = p
  210.  
  211.     def velocity(self, t):
  212.         return self._eval_xyz(self._vel, t)
  213.  
  214.     def acceleration(self, t):
  215.         return self._eval_xyz(self._acc, t)
  216.  
  217.     def jerk(self, t):
  218.         return self._eval_xyz(self._jerk, t)
  219.  
  220.     def _deriv(self, p):
  221.         result = []
  222.         for i in range(3):
  223.             result.append([
  224.                 1 * p[i][1],
  225.                 2 * p[i][2],
  226.                 3 * p[i][3],
  227.                 4 * p[i][4],
  228.                 5 * p[i][5],
  229.                 6 * p[i][6],
  230.                 7 * p[i][7],
  231.                 0
  232.             ])
  233.  
  234.         return result
  235.  
  236.     def _eval(self, p, t):
  237.         result = 0
  238.         for part in range(8):
  239.             result += p[part] * (t ** part)
  240.         return result
  241.  
  242.     def _eval_xyz(self, p, t):
  243.         return np.array(
  244.             [self._eval(p[0], t), self._eval(p[1], t), self._eval(p[2], t)])
  245.  
  246.     def print_poly_python(self):
  247.         s = '  [' + str(self._scale) + ', '
  248.  
  249.         for axis in range(4):
  250.             for d in range(8):
  251.                 s += str(self._polys[axis][d]) + ', '
  252.  
  253.         s += '],  # noqa'
  254.         print(s)
  255.  
  256.     def print_poly_c(self):
  257.         s = ''
  258.  
  259.         for axis in range(4):
  260.             for d in range(8):
  261.                 s += str(self._polys[axis][d]) + ', '
  262.  
  263.         s += str(self._scale)
  264.         print(s)
  265.  
  266.     def print_points(self):
  267.         print(self._points)
  268.  
  269.     def print_peak_vals(self):
  270.         peak_v = 0.0
  271.         peak_a = 0.0
  272.         peak_j = 0.0
  273.  
  274.         step = 0.05
  275.         for t in np.arange(0.0, self._scale + step, step):
  276.             peak_v = max(peak_v, np.linalg.norm(self._eval_xyz(self._vel, t)))
  277.             peak_a = max(peak_a, np.linalg.norm(self._eval_xyz(self._acc, t)))
  278.             peak_j = max(peak_j, np.linalg.norm(self._eval_xyz(self._jerk, t)))
  279.  
  280.         print('Peak v:', peak_v, 'a:', peak_a, 'j:', peak_j)
  281.  
  282.     def _stretch_polys(self, polys, time):
  283.         result = np.zeros([4, 8])
  284.  
  285.         recip = 1.0 / time
  286.  
  287.         for p in range(4):
  288.             scale = 1.0
  289.             for t in range(8):
  290.                 result[p][t] = polys[p][t] * scale
  291.                 scale *= recip
  292.  
  293.         return result
  294.  
  295.     def _scale_control_points(self, unscaled_points, scale):
  296.         s = scale
  297.         l_s = 1 - s
  298.         p = unscaled_points
  299.  
  300.         result = [None] * 8
  301.  
  302.         result[0] = p[0]
  303.         result[1] = l_s * p[0] + s * p[1]
  304.         result[2] = l_s ** 2 * p[0] + 2 * l_s * s * p[1] + s ** 2 * p[2]
  305.         result[3] = l_s ** 3 * p[0] + 3 * l_s ** 2 * s * p[
  306.             1] + 3 * l_s * s ** 2 * p[2] + s ** 3 * p[3]
  307.         result[4] = l_s ** 3 * p[7] + 3 * l_s ** 2 * s * p[
  308.             6] + 3 * l_s * s ** 2 * p[5] + s ** 3 * p[4]
  309.         result[5] = l_s ** 2 * p[7] + 2 * l_s * s * p[6] + s ** 2 * p[5]
  310.         result[6] = l_s * p[7] + s * p[6]
  311.         result[7] = p[7]
  312.  
  313.         return result
  314.  
  315.  
  316. class Visualizer:
  317.     def __init__(self):
  318.         self.canvas = scene.SceneCanvas(keys='interactive', size=(800, 600),
  319.                                         show=True)
  320.         view = self.canvas.central_widget.add_view()
  321.         view.bgcolor = '#ffffff'
  322.         view.camera = TurntableCamera(fov=10.0, distance=40.0, up='+z',
  323.                                       center=(0.0, 0.0, 1.0))
  324.         XYZAxis(parent=view.scene)
  325.         self.scene = view.scene
  326.  
  327.     def marker(self, pos, color='black', size=8):
  328.         Markers(pos=np.array(pos, ndmin=2), face_color=color,
  329.                 parent=self.scene, size=size)
  330.  
  331.     def lines(self, points, color='black'):
  332.         LinePlot(points, color=color, parent=self.scene)
  333.  
  334.     def line(self, a, b, color='black'):
  335.         self.lines([a, b], color)
  336.  
  337.     def run(self):
  338.         self.canvas.app.run()
  339.  
  340.  
  341. segment_time = 2
  342. z = 1
  343. yaw = 0
  344.  
  345. segments = []
  346.  
  347. # Nodes with one control point has not velocity, this is similar to calling
  348. # goto in the High-level commander
  349.  
  350. n0 = Node((0, 0, z, yaw))
  351. n1 = Node((1, 0, z, yaw))
  352. n2 = Node((1, 1, z, yaw))
  353. n3 = Node((0, 1, z, yaw))
  354.  
  355. segments.append(Segment(n0, n1, segment_time))
  356. segments.append(Segment(n1, n2, segment_time))
  357. segments.append(Segment(n2, n3, segment_time))
  358. segments.append(Segment(n3, n0, segment_time))
  359.  
  360.  
  361. # By setting the q1 control point we get velocity through the nodes
  362. # Increase d to 0.7 to get some more action
  363. d = 0.1
  364.  
  365. n5 = Node((1, 0, z, yaw), q1=(1 + d, 0 + d, z, yaw))
  366. n6 = Node((1, 1, z, yaw), q1=(1 - d, 1 + d, z, yaw))
  367. n7 = Node((0, 1, z, yaw), q1=(0 - d, 1 - d, z, yaw))
  368.  
  369. segments.append(Segment(n0, n5, segment_time))
  370. segments.append(Segment(n5, n6, segment_time))
  371. segments.append(Segment(n6, n7, segment_time))
  372. segments.append(Segment(n7, n0, segment_time))
  373.  
  374.  
  375. # When setting q2 we can also control acceleration and get more action.
  376. # Yaw also adds to the fun.
  377.  
  378. d2 = 0.2
  379. dyaw = 2
  380. f = -0.3
  381.  
  382. n8 = Node(
  383.     (1, 0, z, yaw),
  384.     q1=(1 + d2, 0 + d2, z, yaw),
  385.     q2=(1 + 2 * d2, 0 + 2 * d2 + 0*f * d2, 1, yaw))
  386. n9 = Node(
  387.     (1, 1, z, yaw + dyaw),
  388.     q1=(1 - d2, 1 + d2, z, yaw + dyaw),
  389.     q2=(1 - 2 * d2 + f * d2, 1 + 2 * d2 + f * d2, 1, yaw + dyaw))
  390. n10 = Node(
  391.     (0, 1, z, yaw - dyaw),
  392.     q1=(0 - d2, 1 - d2, z, yaw - dyaw),
  393.     q2=(0 - 2 * d2,  1 - 2 * d2,  1, yaw - dyaw))
  394.  
  395. segments.append(Segment(n0, n8, segment_time))
  396. segments.append(Segment(n8, n9, segment_time))
  397. segments.append(Segment(n9, n10, segment_time))
  398. segments.append(Segment(n10, n0, segment_time))
  399.  
  400.  
  401. print('Paste this code into the autonomous_sequence_high_level.py example to '
  402.       'see it fly')
  403. for s in segments:
  404.     s.print_poly_python()
  405.  
  406.  
  407. # Enable this if you have Vispy installed and want a visualization of the
  408. # trajectory
  409. if False:
  410.     # Import here to avoid problems for users that do not have Vispy
  411.     from vispy import scene
  412.     from vispy.scene import XYZAxis, LinePlot, TurntableCamera, Markers
  413.  
  414.     visualizer = Visualizer()
  415.     for s in segments:
  416.         s.draw_trajectory(visualizer)
  417.         # s.draw_vel(visualizer)
  418.         # s.draw_control_points(visualizer)
  419.  
  420.     for n in [n0, n1, n2, n3, n5, n6, n7, n8, n9, n10]:
  421.         n.draw_unscaled_controlpoints(visualizer)
  422.  
  423.     visualizer.run()
  424.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement