From f72ce673045815ebb4a385fa528058672914dc39 Mon Sep 17 00:00:00 2001 From: nyaruko Date: Mon, 5 Mar 2018 16:13:51 +0800 Subject: [PATCH] Allowing change the level of continuity --- minimum_snap_trajectory_generation/README.md | 19 +++ minimum_snap_trajectory_generation/a.fig | Bin 0 -> 37989 bytes minimum_snap_trajectory_generation/arrangeT.m | 6 + .../calc_tvec.m | 7 + minimum_snap_trajectory_generation/computeQ.m | 19 +++ .../demo1_minimum_snap_simple.m | 110 +++++++++++++ .../demo2_minimum_snap_corridor.m | 125 +++++++++++++++ .../demo3_minimum_snap_close_form.m | 140 +++++++++++++++++ .../demo4_minimum_snap_guiding.m | 148 ++++++++++++++++++ .../estimate/P2ndOrder.m | 3 + .../estimate/P3rdOrder.m | 3 + .../estimate/ParaGen.m | 79 ++++++++++ .../estimate/RefGen.m | 20 +++ .../estimate/V2ndOrder.m | 3 + minimum_snap_trajectory_generation/poly_val.m | 14 ++ .../polys_vals.m | 17 ++ .../shortestTime_synced.m | 12 ++ .../transform/FlightCorridor.m | 130 +++++++++++++++ .../transform/global2local.m | 43 +++++ .../transform/pitchMatrix.m | 7 + .../transform/switch2nextTarget.m | 28 ++++ .../transform/tiltConstraintsApprx.m | 34 ++++ .../transform/tiltMaxRect.m | 49 ++++++ .../transform/transMatrix.m | 4 + .../transform/transRot.m | 6 + .../transform/yawMatrix.m | 7 + 26 files changed, 1033 insertions(+) create mode 100644 minimum_snap_trajectory_generation/README.md create mode 100644 minimum_snap_trajectory_generation/a.fig create mode 100644 minimum_snap_trajectory_generation/arrangeT.m create mode 100644 minimum_snap_trajectory_generation/calc_tvec.m create mode 100644 minimum_snap_trajectory_generation/computeQ.m create mode 100644 minimum_snap_trajectory_generation/demo1_minimum_snap_simple.m create mode 100644 minimum_snap_trajectory_generation/demo2_minimum_snap_corridor.m create mode 100644 minimum_snap_trajectory_generation/demo3_minimum_snap_close_form.m create mode 100644 minimum_snap_trajectory_generation/demo4_minimum_snap_guiding.m create mode 100644 minimum_snap_trajectory_generation/estimate/P2ndOrder.m create mode 100644 minimum_snap_trajectory_generation/estimate/P3rdOrder.m create mode 100644 minimum_snap_trajectory_generation/estimate/ParaGen.m create mode 100644 minimum_snap_trajectory_generation/estimate/RefGen.m create mode 100644 minimum_snap_trajectory_generation/estimate/V2ndOrder.m create mode 100644 minimum_snap_trajectory_generation/poly_val.m create mode 100644 minimum_snap_trajectory_generation/polys_vals.m create mode 100644 minimum_snap_trajectory_generation/shortestTime_synced.m create mode 100644 minimum_snap_trajectory_generation/transform/FlightCorridor.m create mode 100644 minimum_snap_trajectory_generation/transform/global2local.m create mode 100644 minimum_snap_trajectory_generation/transform/pitchMatrix.m create mode 100644 minimum_snap_trajectory_generation/transform/switch2nextTarget.m create mode 100644 minimum_snap_trajectory_generation/transform/tiltConstraintsApprx.m create mode 100644 minimum_snap_trajectory_generation/transform/tiltMaxRect.m create mode 100644 minimum_snap_trajectory_generation/transform/transMatrix.m create mode 100644 minimum_snap_trajectory_generation/transform/transRot.m create mode 100644 minimum_snap_trajectory_generation/transform/yawMatrix.m diff --git a/minimum_snap_trajectory_generation/README.md b/minimum_snap_trajectory_generation/README.md new file mode 100644 index 0000000..cddf954 --- /dev/null +++ b/minimum_snap_trajectory_generation/README.md @@ -0,0 +1,19 @@ +# minimum snap trajectory planning in MATLAB +This repository contains sample code in MATLAB for minimum snap trajectory planning described in http://blog.csdn.net/q597967420/article/details/73647190. +This README provides a brief overview of our trajectory generation utilities with some examples. + +## Required +1. MATLAB(R2013a is tested) + +## How to run +There are 4 demo codes in 2D space, you can directly run these and see results. + + - demo1: minimum snap trajectory planning with waypoints **strong constrants**(equality constraints). + - demo2: minimum snap trajectory planning with **corridor constraints**(iequality constraints). + - demo3: minimum snap trajectory planning with waypoints strong constrants by **close form** solution. + - demo4: minimum snap trajectory planning with **guiding path** + +## Licence +The source code is released under GPLv3 license. + +Any problem, please contact maoshuyuan123@gmail.com \ No newline at end of file diff --git a/minimum_snap_trajectory_generation/a.fig b/minimum_snap_trajectory_generation/a.fig new file mode 100644 index 0000000000000000000000000000000000000000..db950cda46928b5094671507bd038c7c5d59fbbe GIT binary patch literal 37989 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 + t1 = tk(i,k-j+1); + t2 = tk(i+1,k-j+1); + end + A(n_continuous*2*(i-1)+j,n_coef*(i-1)+k) = prod(k-j+1:k-1)*t1; + A(n_continuous*2*(i-1)+n_continuous+j,n_coef*(i-1)+k) = prod(k-j+1:k-1)*t2; + end + end +end + +% compute M +M = zeros(n_poly*2*n_continuous,n_continuous*(n_poly+1)); +for i = 1:n_poly*2 + j = floor(i/2)+1; + rbeg = n_continuous*(i-1)+1; + cbeg = n_continuous*(j-1)+1; + M(rbeg:rbeg+n_continuous-1,cbeg:cbeg+n_continuous-1) = eye(n_continuous); +end + +% compute C +num_d = n_continuous*(n_poly+1); +C = eye(num_d); +df = [wayp,v0,a0,j0,v1,a1,j1]';% fix all pos(n_poly+1) + start va(2) + end va(2) +fix_idx = [1:4:num_d,2,3,4,num_d-2,num_d-1,num_d]; +free_idx = setdiff(1:num_d,fix_idx); +C = [C(:,fix_idx) C(:,free_idx)]; + +AiMC = inv(A)*M*C; +R = AiMC'*Q_all*AiMC; + +n_fix = length(fix_idx); +Rff = R(1:n_fix,1:n_fix); +Rpp = R(n_fix+1:end,n_fix+1:end); +Rfp = R(1:n_fix,n_fix+1:end); +Rpf = R(n_fix+1:end,1:n_fix); + +dp = -inv(Rpp)*Rfp'*df; + +p = AiMC*[df;dp]; + +polys = reshape(p,n_coef,n_poly); + +end + diff --git a/minimum_snap_trajectory_generation/demo4_minimum_snap_guiding.m b/minimum_snap_trajectory_generation/demo4_minimum_snap_guiding.m new file mode 100644 index 0000000..cc8a028 --- /dev/null +++ b/minimum_snap_trajectory_generation/demo4_minimum_snap_guiding.m @@ -0,0 +1,148 @@ +function demo4_minimum_snap_guiding() +clear,clc; + +%% condition +waypts = [0,0; + 1,2; + 2,0; + 4,5; + 5,2]'; +v0 = [0,0]; +a0 = [0,0]; +v1 = [0,0]; +a1 = [0,0]; +T = 5; +n_order = 5; +lambda = 10; %guiding weight + +%% sample mid points +r = 0.5; %% corridor r +step = r; +new_waypts = waypts(:,1); +for i=2:size(waypts,2) + x1 = waypts(1,i-1); + y1 = waypts(2,i-1); + x2 = waypts(1,i); + y2 = waypts(2,i); + n = ceil(hypot(x1-x2,y1-y2)/step)+1; + sample_pts = [linspace(x1,x2,n);linspace(y1,y2,n)]; + new_waypts = [new_waypts sample_pts(:,2:end)]; +end +ts = arrangeT(new_waypts,T); + +figure(1) +plot_idx = 221; +for lambda = [0,10,100,10000] + subplot(plot_idx); + %% trajectory plan + polys_x = minimum_snap_single_axis_guiding_path(new_waypts(1,:),ts,n_order,v0(1),a0(1),v1(1),a1(1),r,lambda); + polys_y = minimum_snap_single_axis_guiding_path(new_waypts(2,:),ts,n_order,v0(2),a0(2),v1(2),a1(2),r,lambda); + + %% result show + plot(new_waypts(1,:),new_waypts(2,:),'.g');hold on; + plot(waypts(1,:),waypts(2,:),'*r');hold on; + for i=1:size(new_waypts,2) + plot_rect(new_waypts(:,i),r); + end + title(['\lambda = ' num2str(lambda)]); + tt = 0:0.01:T; + xx = polys_vals(polys_x,ts,tt,0); + yy = polys_vals(polys_y,ts,tt,0); + plot(xx,yy,'r'); + plot_idx = plot_idx+1; +end + +end + +function plot_rect(center,r) +p1 = center+[-r;-r]; +p2 = center+[-r;r]; +p3 = center+[r;r]; +p4 = center+[r;-r]; +plot_line(p1,p2); +plot_line(p2,p3); +plot_line(p3,p4); +plot_line(p4,p1); +end + +function plot_line(p1,p2) +a = [p1(:),p2(:)]; +plot(a(1,:),a(2,:),'b'); +end + +function polys = minimum_snap_single_axis_guiding_path(waypts,ts,n_order,v0,a0,ve,ae,corridor_r,lambda) +p0 = waypts(1); +pe = waypts(end); + +n_poly = length(waypts)-1; +n_coef = n_order+1; + +% compute Q +Q_all = []; +for i=1:n_poly + Q_all = blkdiag(Q_all,computeQ(n_order,3,ts(i),ts(i+1))); +end +b_all = zeros(size(Q_all,1),1); + +% add guiding pos +H_guide = []; +b_guide = []; +for i = 1:n_poly + t1 = ts(i); + t2 = ts(i+1); + p1 = waypts(i); + p2 = waypts(i+1); + a1 = (p2-p1)/(t2-t1); + a0 = p1-a1*t1; + ci = zeros(n_coef,1); + ci(1:2,1) = [a0;a1]; %guiding polynormial with linear curve + Qi = computeQ(n_order,0,t1,t2); + bi = -Qi'*ci; + H_guide = blkdiag(H_guide,Qi); + b_guide = [b_guide;bi]; +end +Q_all = Q_all+lambda*H_guide; +b_all = b_all+lambda*b_guide; + +Aeq = zeros(3*n_poly+3,n_coef*n_poly); +beq = zeros(3*n_poly+3,1); + +% start/terminal pva constraints (6 equations) +Aeq(1:3,1:n_coef) = [calc_tvec(ts(1),n_order,0); + calc_tvec(ts(1),n_order,1); + calc_tvec(ts(1),n_order,2)]; +Aeq(4:6,n_coef*(n_poly-1)+1:n_coef*n_poly) = ... + [calc_tvec(ts(end),n_order,0); + calc_tvec(ts(end),n_order,1); + calc_tvec(ts(end),n_order,2)]; +beq(1:6,1) = [p0,v0,a0,pe,ve,ae]'; +neq = 6; + +% continuous constraints ((n_poly-1)*3 equations) +for i=1:n_poly-1 + tvec_p = calc_tvec(ts(i+1),n_order,0); + tvec_v = calc_tvec(ts(i+1),n_order,1); + tvec_a = calc_tvec(ts(i+1),n_order,2); + neq=neq+1; + Aeq(neq,n_coef*(i-1)+1:n_coef*(i+1))=[tvec_p,-tvec_p]; + neq=neq+1; + Aeq(neq,n_coef*(i-1)+1:n_coef*(i+1))=[tvec_v,-tvec_v]; + neq=neq+1; + Aeq(neq,n_coef*(i-1)+1:n_coef*(i+1))=[tvec_a,-tvec_a]; +end + +% corridor constraints (n_ploy-1 iequations) +Aieq = zeros(2*(n_poly-1),n_coef*n_poly); +bieq = zeros(2*(n_poly-1),1); +for i=1:n_poly-1 + tvec_p = calc_tvec(ts(i+1),n_order,0); + Aieq(2*i-1:2*i,n_coef*i+1:n_coef*(i+1)) = [tvec_p;-tvec_p]; + bieq(2*i-1:2*i) = [waypts(i+1)+corridor_r corridor_r-waypts(i+1)]; +end + +p = quadprog(Q_all,b_all,Aieq,bieq,Aeq,beq); + +polys = reshape(p,n_coef,n_poly); + +end + diff --git a/minimum_snap_trajectory_generation/estimate/P2ndOrder.m b/minimum_snap_trajectory_generation/estimate/P2ndOrder.m new file mode 100644 index 0000000..5d752d0 --- /dev/null +++ b/minimum_snap_trajectory_generation/estimate/P2ndOrder.m @@ -0,0 +1,3 @@ +function [x] = P2ndOrder(t,x0,v0,a) + +x = x0+v0*t+0.5*a*t^2; \ No newline at end of file diff --git a/minimum_snap_trajectory_generation/estimate/P3rdOrder.m b/minimum_snap_trajectory_generation/estimate/P3rdOrder.m new file mode 100644 index 0000000..7b0a7ec --- /dev/null +++ b/minimum_snap_trajectory_generation/estimate/P3rdOrder.m @@ -0,0 +1,3 @@ +function [x] = P3rdOrder(t,x0,v0,a0,j) + +x = x0+v0*t+0.5*a0*t^2+1/6*j*t^3; \ No newline at end of file diff --git a/minimum_snap_trajectory_generation/estimate/ParaGen.m b/minimum_snap_trajectory_generation/estimate/ParaGen.m new file mode 100644 index 0000000..a147cc2 --- /dev/null +++ b/minimum_snap_trajectory_generation/estimate/ParaGen.m @@ -0,0 +1,79 @@ +function Params = ParaGen(vf, state, aM, jM) +%input: +%vf: velocity setpoint +%state: initial state +%aM,jM: max acc and jerk +% +%output: +%Params [T1,T2,T3,j1,j3,a(0~3),v(0~3),p(0~3)]: +%For reconstructing the trajectory + +% Calculate the velocity when acc driectly comes to zero +%------------------------------------------------------- +v_stop = state.a^2 / 2 / jM * sign(state.a) + state.v; + +% Deteremin the cruise direction and cruise acc +%------------------------------------------------------- +d = sign(vf - v_stop); +cruse_acc = d * aM; + +% Calculate the phase 1 params +%------------------------------------------------------- +t1 = abs(cruse_acc - state.a) / jM; +Params.j1 = jM * sign(cruse_acc-state.a); +v1 = state.v + state.a * t1 + 0.5 * Params.j1 * t1^2; + +% Calculate the pahse 3 params +%------------------------------------------------------- +t3 = abs(-cruse_acc) / jM; +Params.j3 = jM * sign(-cruse_acc); +v3bar = cruse_acc * t3 + 0.5 * Params.j3 * t3^2; + +% Calculate the phase 2 params +%------------------------------------------------------- +v2bar = vf - v1 - v3bar; + +if d == 0 + t2 = 0; +else + t2 = v2bar / cruse_acc; +end + +% There is a cruise phase iff t2 >= 0 +%------------------------------------------------------ +if t2 >= 0 + Params.T1 = t1; + Params.T2 = t1+t2; + Params.T3 = t1+t2+t3; +else + % Here a_norm stands for the maximum reachable acc if the cruse acc now + % cannot be reached + %---------------------------------------------------------------------- + a_norm = d * sqrt(d * jM * (vf - state.v) + 0.5 * state.a^2); + t1 = abs(a_norm - state.a) / jM; + t2 = 0; + t3 = abs(0 - a_norm)/jM; + Params.T1 = t1; + Params.T2 = t1+t2; + Params.T3 = t1+t2+t3; +end + +%Complete the params +%--------------------------------------------------------------------------- +Params.a0 = state.a; +Params.v0 = state.v; +Params.p0 = state.p; + +Params.a1 = V2ndOrder(Params.T1, Params.a0, Params.j1); +Params.v1 = P2ndOrder(Params.T1, Params.v0, Params.a0, Params.j1); +Params.p1 = P3rdOrder(Params.T1, Params.p0, Params.v0, Params.a0, Params.j1); + +Params.a2 = V2ndOrder(Params.T2 - Params.T1, Params.a1, 0); +Params.v2 = P2ndOrder(Params.T2 - Params.T1, Params.v1, Params.a1, 0); +Params.p2 = P3rdOrder(Params.T2 - Params.T1, Params.p1, Params.v1, Params.a1, 0); + +Params.a3 = V2ndOrder(Params.T3 - Params.T2, Params.a2, Params.j3); +Params.v3 = P2ndOrder(Params.T3 - Params.T2, Params.v2, Params.a2, Params.j3); +Params.p3 = P3rdOrder(Params.T3 - Params.T2, Params.p2, Params.v2, Params.a2, Params.j3); + +end \ No newline at end of file diff --git a/minimum_snap_trajectory_generation/estimate/RefGen.m b/minimum_snap_trajectory_generation/estimate/RefGen.m new file mode 100644 index 0000000..0f466e2 --- /dev/null +++ b/minimum_snap_trajectory_generation/estimate/RefGen.m @@ -0,0 +1,20 @@ +%Generate the path reference using the given parameters +function [Ostate] = RefGen(Params, t) + +if t=Params.T1 && t=Params.T2 && tts(idx+1)+0.0001 + idx = idx+1; + end + vals(i) = poly_val(polys(:,idx),t,r); + end +end +end + diff --git a/minimum_snap_trajectory_generation/shortestTime_synced.m b/minimum_snap_trajectory_generation/shortestTime_synced.m new file mode 100644 index 0000000..4ecf869 --- /dev/null +++ b/minimum_snap_trajectory_generation/shortestTime_synced.m @@ -0,0 +1,12 @@ +function TM = shortestTime_synced(wp1,wp2,vM,aM) +%vM/aM = [horizon vertical]; +[G2L,L2G,yaw,pitch] = global2local(wp1',wp2'); +[vMc,aMc,jMc]=tiltConstraintsApprx(vM',aM',aM',pitch); + +state.p = 0; +state.v = 0; +state.a = 0; + +Params = ParaGen(norm(wp2-wp1), state, vMc(1), aMc(1)); +TM = Params.T3; +end \ No newline at end of file diff --git a/minimum_snap_trajectory_generation/transform/FlightCorridor.m b/minimum_snap_trajectory_generation/transform/FlightCorridor.m new file mode 100644 index 0000000..b78dc4c --- /dev/null +++ b/minimum_snap_trajectory_generation/transform/FlightCorridor.m @@ -0,0 +1,130 @@ +classdef FlightCorridor < handle + %FLIGHTCORRIDOR: a light corridor defined in terms of a rotated cuboid. + + properties (Access = public) + sfc = [1,1,1]'; % The safe corridor distance to three dimensions + a = [0,0,0]'; % The start point + b = [0,0,0]'; % The end point + l = 0; % Corridor length + yaw = 0; + pitch = 0; + G2L; % Transformation matrix global to local + L2G; % Transformation matrix local to global + vMc; % Max velocity in local frame + aMc; % Max acceleration in local frame + jMc; % Max jerk in local frame + target; %Target in local frame + end + + methods + function this = FlightCorridor(a,b,sfc,vM,aM,jM) + this.a = a; + this.b = b; + this.sfc = sfc; + this.l = norm(b-a); + [this.G2L,this.L2G,this.yaw,this.pitch] = global2local(a,b); + [this.vMc,this.aMc,this.jMc]=tiltConstraintsApprx(vM,aM,jM,this.pitch); + this.target = [this.l 0 0]'; + end + %--- + function sfc = getSfc(this,i) + sfc = this.sfc(i); + end + %--- + function yaw = getYaw(this) + yaw = this.yaw; + end + %--- + function l = getL(this) + l = this.l; + end + %--- + function pitch = getPitch(this) + pitch = this.pitch; + end + %--- + function out = getVmc(this,i) + out = this.vMc(i); + end + %--- + function out = getAmc(this,i) + out = this.aMc(i); + end + %--- + function out = getJmc(this,i) + out = this.jMc(i); + end + %--- + function target = getTarget(this,i) + target = this.target(i); + end + %--- + function vec_out = l2g(this, vec_in,do_translation) + if(do_translation) + vec_out = transRot(this.L2G.pos, vec_in); + else + vec_out = transRot(this.L2G.free,vec_in); + end + end + %--- + function vec_out = g2l(this, vec_in,do_translation) + if(do_translation) + vec_out = transRot(this.G2L.pos, vec_in); + else + vec_out = transRot(this.G2L.free,vec_in); + end + end + %--- + function G2L = getG2L(this) + G2L = this.G2L; + end + %--- + function L2G = getL2G(this) + L2G = this.L2G; + end + %--- + function inside = isPointInside(this,p,is_local_cord) + if (~is_local_cord) + p = this.g2l(p); + end + + inside = this.isInsideLocalCuboid(p); + end + %--- + function inside = isInsideLocalCuboid(this,p) + inside = true; + %---Consider the all three face + if (p(1) < -this.sfc(1) || p(1) > this.sfc(1)+this.l) + inside = false; + return; + end + for i=2:3 + if(abs(p(i))>this.sfc(i)) + inside = false; + return; + end + end + end + %--- + function inside = isMaxMinValueInside(this,max_p,min_p) + inside = true; + + if max_p(1) < -this.sfc(1) || max_p(1) > this.sfc(1) + this.l + inside = false; + return; + end + if min_p(1) < -this.sfc(1) || min_p(1) > this.sfc(1) + this.l + inside = false; + return; + end + + for i=2:3 + if abs(max_p(i))>this.sfc(i) || abs(min_p(i))>this.sfc(i) + inside = false; + return; + end + end + end + end +end + diff --git a/minimum_snap_trajectory_generation/transform/global2local.m b/minimum_snap_trajectory_generation/transform/global2local.m new file mode 100644 index 0000000..46def62 --- /dev/null +++ b/minimum_snap_trajectory_generation/transform/global2local.m @@ -0,0 +1,43 @@ +function [G2L,L2G,yaw,pitch] = global2local(a,b) +%This function transfer the global pos vel acc into a corridor frame +%that its x-axis is along the line from a to b, its origin is at a, +%its y-axis is parrlel to the global y. +%---Input: +%a: the starting point of this corridor +%b: the end point of this corridor +%pos: position in global frame +%vel: velocity in global frame +%acc: acceleration in global frame +%---Output: +%G2L: the transformation matrix +%L2G: the reverse transformation matrix +%yaw: yaw angle in rad +%pitch: pitch angle in rad +%__________________________________________________________________________ + +vec = b - a; +%---Yaw rotation +yaw = atan2(vec(2),vec(1)); +%---forward +T_yaw = yawMatrix(yaw); +%---reverse +T_yaw_r = yawMatrix(-yaw); + +%---Pitch rotation +pitch = atan2(vec(3),norm(vec(1:2))); +%---forward +T_pitch = pitchMatrix(pitch); +%---reverse +T_pitch_r = pitchMatrix(-pitch); + +%---Translation +T_trans = transMatrix(-a); +T_trans_r = transMatrix(a); + +%---Outputs +G2L.free = T_pitch*T_yaw; +G2L.pos = T_pitch*T_yaw*T_trans; + +L2G.free = T_yaw_r*T_pitch_r; +L2G.pos = T_trans_r*T_yaw_r*T_pitch_r; + diff --git a/minimum_snap_trajectory_generation/transform/pitchMatrix.m b/minimum_snap_trajectory_generation/transform/pitchMatrix.m new file mode 100644 index 0000000..513ffa4 --- /dev/null +++ b/minimum_snap_trajectory_generation/transform/pitchMatrix.m @@ -0,0 +1,7 @@ +function A = pitchMatrix(pitch) +A = [cos(pitch), 0, sin(pitch), 0; + 0, 1, 0, 0; + -sin(pitch), 0, cos(pitch), 0; + 0, 0, 0, 1; + ]; +end \ No newline at end of file diff --git a/minimum_snap_trajectory_generation/transform/switch2nextTarget.m b/minimum_snap_trajectory_generation/transform/switch2nextTarget.m new file mode 100644 index 0000000..7efb0e7 --- /dev/null +++ b/minimum_snap_trajectory_generation/transform/switch2nextTarget.m @@ -0,0 +1,28 @@ +function change2next = switch2nextTarget(SFCD,c,d,gs,aM,jM) +%whether vehicle is already in, whether the vehicle will stop in +isVehicle_in = 0; +isStop_in = 0; + +%project the current global states along the line c-d +[xs, ys, ~, ~] = ThreeDTransform(c', d', gs); + +%calculate whether the vehcile is within the c-d corridor +dist = norm(c-d); +if(xs.p>-SFCD && xs.p-SFCD && ys.p-SFCD && stp_x-SFCD && stp_y=h); +if(width_is_longer) + side_long = w; + side_short = h; +else + side_long = h; + side_short = w; +end + +% since the solutions for angle, -angle and 180-angle are all the same, +% if suffices to look at the first quadrant and the absolute values of sin,cos: +sin_a = abs(sin(angle)); +cos_a = abs(cos(angle)); + +if(side_short <= 2.0*sin_a*cos_a*side_long) +% half constrained case: two crop corners touch the longer side, +% the other two corners are on the mid-line parallel to the longer line + x = 0.5*side_short; + if(width_is_longer) + wr = x/sin_a; + hr = x/cos_a; + else + wr = x/cos_a; + hr = x/sin_a; + end +else +% fully constrained case: crop touches all 4 sides + cos_2a = cos_a*cos_a - sin_a*sin_a; + wr = (w*cos_a - h*sin_a)/cos_2a; + hr = (h*cos_a - w*sin_a)/cos_2a; +end \ No newline at end of file diff --git a/minimum_snap_trajectory_generation/transform/transMatrix.m b/minimum_snap_trajectory_generation/transform/transMatrix.m new file mode 100644 index 0000000..cb51990 --- /dev/null +++ b/minimum_snap_trajectory_generation/transform/transMatrix.m @@ -0,0 +1,4 @@ +function A = transMatrix(v) +A = eye(4); +A(:,4) = [v;1]; +end \ No newline at end of file diff --git a/minimum_snap_trajectory_generation/transform/transRot.m b/minimum_snap_trajectory_generation/transform/transRot.m new file mode 100644 index 0000000..61d4d2d --- /dev/null +++ b/minimum_snap_trajectory_generation/transform/transRot.m @@ -0,0 +1,6 @@ +function c = transRot(A,b) +b_in = [b;1]; +c_in = A*b_in; +c = c_in(1:3); +end + diff --git a/minimum_snap_trajectory_generation/transform/yawMatrix.m b/minimum_snap_trajectory_generation/transform/yawMatrix.m new file mode 100644 index 0000000..c002c5a --- /dev/null +++ b/minimum_snap_trajectory_generation/transform/yawMatrix.m @@ -0,0 +1,7 @@ +function A = yawMatrix(yaw) +A = [cos(yaw), sin(yaw), 0, 0; + -sin(yaw), cos(yaw), 0, 0; + 0, 0, 1, 0; + 0, 0, 0, 1; + ]; +end \ No newline at end of file