多姿态插补用于多个连续的姿态,此处采用Squad插值,贝塞尔相关参考深入理解贝塞尔曲线

一、中间点s的确定

Squad在控制点的姿态上是平滑的 因此从速度平滑上找到中间点 si需要满足两式相等,因此 程序实现  
function [qa] = get_intermediate_control_point(j,q)
% 插补中间点
% 若点为起点和终点,则直接返回当前值
L = size(q,2);
if(j==1)
    qa =  q(:,1)';
    return;
elseif(j==L)
    qa =  q(:,L)';
    return;
else
    qji=quatinv(q(:,j)');
    qiqm1=quatmultiply(qji,q(:,j-1)');
    qiqp1=quatmultiply(qji,q(:,j+1)');
    ang_vel =-((quatlog(qiqp1)+quatlog(qiqm1))/4); 

    qa = quatmultiply(q(:,j)',quatexp(ang_vel));
    %qa = quatnormalize(qa);
end
end

二、插补间距的确定

当插补姿态为一系列姿态时,全局插补分割仍沿用[0,1]分割,但需要映射到不同组姿态内(在进行具体插补时,依然以两个姿态一组进行插补,但调用函数时是整个系列的全局分割)。 程序实现  
function alpha = eval_alpha(s,i,L)
% 将s[0,1]映射到q1-q4的范围内,判断s在哪两个q之间,然后在计算s在对应区间的值,
% 如L=4,s=0.7,则k=3.1,插值应该在q3,q4之间(3.1-4.1),插值为0.1,
% 若i不在范围内,直接返回0不计算
k = s*(L-1)+1;

if((i>=k)&&(i<k+1))
    alpha=k-(i-1);
else
    alpha=0;
end

end

三、slerp插值

slerp标准公式 程序实现  
q1_inv    = quatinv(q1);
q1_inv_q2 = quatmultiply(q1_inv,q2);
omega     = quatlog(q1_inv_q2);
q_out       = quatmultiply(q1,quatexp(omega*t));

四、实现效果

function [val] =  quat_squad(q,s)

L = size(q,2);
val = q(:,1)';

% 若点积小于0进行取负,确保两姿态之间取最短路径
for j=2:L
    C = dot(q(:,j-1),q(:,j));
    if(C<0)
        q(:,j) = -q(:,j);
    end 
end

% 若时间在0,1则直接返回起点和终点
if s==0 
    val=q(:,1);
    return;
elseif s==1
    val=q(:,end);
    return;
end

for j =2:L
    % 全局细分映射到局部细分
    alpha=eval_alpha(s,j,L); 
    t= alpha;

    if(alpha>0)
        EPS = 1e-9;

        % 计算两个姿态的点积,结果范围[-1,1]
        C = dot(q(:,j-1),q(:,j));

        if ((1 - C) <= EPS) 
            % 姿态之间过于接近采用线性插补
            val=q(:,j-1)'*(1-s)+q(:,j)'*s; 
            val = quatnormalize(val);
        return;
        end

        if((1 + C) <= EPS)
            % 当姿态夹角接近180,无最短路径,结果不确定
            % 将角度旋转90
            qtemp(1) = q(4,j); qtemp(2) = -q(3,j); qtemp(3)= q(2,j); qtemp(4) = -q(1,j);
            q(:,j) = qtemp';
        end

        % 计算中间点
        qa = get_intermediate_control_point(j-1,q);
        qap1 = get_intermediate_control_point(j,q);

        % 插补
        qtemp1 = do_slerp(q(:,j-1)', q(:,j)', t);
        qtemp2 = do_slerp(qa, qap1, t);
        squad = do_slerp(qtemp1, qtemp2, 2*t*(1-t));
        val = squad;val = quatnormalize(val);
    return;
    end

end

end

function [qa] = get_intermediate_control_point(j,q)
% 插补中间点
% 若点为起点和终点,则直接返回当前值
L = size(q,2);
if(j==1)
    qa =  q(:,1)';
    return;
elseif(j==L)
    qa =  q(:,L)';
    return;
else
    qji=quatinv(q(:,j)');
    qiqm1=quatmultiply(qji,q(:,j-1)');
    qiqp1=quatmultiply(qji,q(:,j+1)');
    ang_vel =-((quatlog(qiqp1)+quatlog(qiqm1))/4); 

    qa = quatmultiply(q(:,j)',quatexp(ang_vel));
    %qa = quatnormalize(qa);
end
end

function alpha = eval_alpha(s,i,L)
% 全局细分到局部细分
k = s*(L-1)+1;

if((i>=k)&&(i<k+1))
    alpha=k-(i-1);
else
    alpha=0;
end

end

function  q_out = do_slerp(q1,q2,t)
% slerp插值
EPS = 1e-9;
C = dot(q1,q2);

if ((1 - C) <= EPS) 
    % 两姿态点积接近1,即夹角非常接近于0,采用直线插补
    q_out=q1*(1-t)+q2*t; % avoiding divisions by number close to 0
    q_out = quatnormalize(q_out);
    return;
elseif((1 + C) <= EPS) 
    % 两姿态点积接近-1,即夹角非常接近于180
    qtemp(1) = q2(4); qtemp(2) = -q2(3); qtemp(3)= q2(2); qtemp(4) = -q2(1); % rotating one of the unit quaternions by 90 degrees -> q2
    q2 =qtemp;
end
% 插补
q1_inv    = quatinv(q1);
q1_inv_q2 = quatmultiply(q1_inv,q2);
omega     = quatlog(q1_inv_q2);
q_out = quatmultiply(q1,quatexp(omega*t));
%q_out = quatnormalize(q_out);
end
参考 Quaternions, Interpolation and Animation-6.2 Interpolation over a series of rotations: Heuristic approach Eberly, David. “Quaternion algebra and calculus.” Magic Software, Inc 21 (2002).