Как поднять прямоугольники, чтобы создать гибкую 3D замкнутую трубу в MATLAB? - PullRequest
1 голос
/ 25 апреля 2019

У меня есть ряд прямоугольников и я знаю точное местоположение их соответствующих 4 углов. Я хотел бы построить их и пролистать каждый из них, чтобы создать нечто вроде трехмерной трубы прямоугольного сечения. Точки также не должны ограничиваться прямой линией. Это должно быть гибким в принятии отклонений. Я также хотел бы, чтобы два конца были закрыты.

Я видел похожий вопрос о лофтинге на вашем сайте в разделе «Как поднять с помощью эллипсов, чтобы создать трехмерную полую трубу в MATLAB или Python?». Ответ произвел на меня впечатление, но, к сожалению, для эллипсов и кругов. Я пытался заставить его работать с прямоугольниками, но не смог понять необходимую логику. Я также попытался исправить все вместе, но это привело к образованию острых краев, которые я не хочу. Мой код выглядит примерно так:

A = importdata(filename);

[size_A, ~] = size(A.data);
axis vis3d;

for i=1:12:size_A-12

    X1 = A.data(i+1); X2 = A.data(i+4); X3 = A.data (i+7); X4= A.data (i+10);
    Y1 = A.data(i+2); Y2 = A.data(i+5); Y3 = A.data(i+8); Y4 = A.data(i+11);
    Z1 = A.data(i+3); Z2 = A.data(i+6); Z3 = A.data(i+9); Z4 = A.data(i+12);

    X= [X1;X2;X3;X4]; 
    Y= [Y1;Y2;Y3;Y4]; 
    Z= [Z1;Z2;Z3;Z4]; 


    plot3(X, Y, Z)
    patch(X, Y, Z, 'g'); %% for the particular planes


    if(i>1) %% for the patching between two planes

        A1= [ X1 X1 X2 X4; a1 X4 a2 X3; a2 a4 a3 a3; X2 a1 X3 a4];
        B1= [ Y1 Y1 Y2 Y4; b1 Y4 b2 Y3; b2 b4 b3 b3; Y2 b1 Y3 b4];
        C1= [ Z1 Z1 Z2 Z4; c1 Z4 c2 Z3; c2 c4 c3 c3; Z2 c1 Z3 c4];

        plot3(A1, B1, C1)
        patch(A1, B1, C1, 'g');

    end

    a1=X1; a2=X2; a3=X3; a4=X4;
    b1=Y1; b2=Y2; b3=Y3; b4=Y4; 
    c1=Z1; c2=Z2; c3=Z3; c4=Z4;

    figure(1)
    grid on
    axis equal
    hold on
    xlabel('x');
    ylabel('y');
    zlabel('z');

end    

Конечный результат должен быть похож на очень пышную трубу с прямоугольными сечениями. Здесь не должно быть острых углов.

PS: файл MATLAB импортирует текстовый документ в формате блокнота, куда будут вводиться координаты, как показано ниже -

Number_of_sections= 7

X_coordinate= 60.00
Y_coordinate= 13.00
Z_coordinate= -7.50
X_coordinate= 60.00
Y_coordinate= -13.00
Z_coordinate= -7.50
X_coordinate= 60.00
Y_coordinate= -13.00
Z_coordinate= -12.50
X_coordinate= 60.00
Y_coordinate= 13.00
Z_coordinate= -12.50

X_coordinate= 95.00
Y_coordinate= 13.00
Z_coordinate= -7.50
X_coordinate= 95.00
Y_coordinate= -13.00
Z_coordinate= -7.50
X_coordinate= 95.00
Y_coordinate= -13.00
Z_coordinate= -12.50
X_coordinate= 95.00
Y_coordinate= 13.00
Z_coordinate= -12.50

X_coordinate= 95.50
Y_coordinate= 13.50
Z_coordinate= -7.50
X_coordinate= 95.50
Y_coordinate= -13.50
Z_coordinate= -7.50
X_coordinate= 95.50
Y_coordinate= -13.50
Z_coordinate= -12.50
X_coordinate= 95.50
Y_coordinate= 13.50
Z_coordinate= -12.50

X_coordinate= 96.00
Y_coordinate= 14.00
Z_coordinate= -7.50
X_coordinate= 96.00
Y_coordinate= -14.00
Z_coordinate= -7.50
X_coordinate= 96.00
Y_coordinate= -14.00
Z_coordinate= -12.50
X_coordinate= 96.00
Y_coordinate= 14.00
Z_coordinate= -12.50

X_coordinate= 96.50
Y_coordinate= 14.50
Z_coordinate= -7.50
X_coordinate= 96.50
Y_coordinate= -14.50
Z_coordinate= -7.50
X_coordinate= 96.50
Y_coordinate= -14.50
Z_coordinate= -12.50
X_coordinate= 96.50
Y_coordinate= 14.50
Z_coordinate= -12.50

X_coordinate= 97.00
Y_coordinate= 15.00
Z_coordinate= -7.50
X_coordinate= 97.00
Y_coordinate= -15.00
Z_coordinate= -7.50
X_coordinate= 97.00
Y_coordinate= -15.00
Z_coordinate= -12.50
X_coordinate= 97.00
Y_coordinate= 15.00
Z_coordinate= -12.50

X_coordinate= 99.00
Y_coordinate= 15.00
Z_coordinate= -7.50
X_coordinate= 99.00
Y_coordinate= -15.00
Z_coordinate= -7.50
X_coordinate= 99.00
Y_coordinate= -15.00
Z_coordinate= -12.50
X_coordinate= 99.00
Y_coordinate= 15.00
Z_coordinate= -12.50

X_coordinate= 99.250
Y_coordinate= 14.7500
Z_coordinate= -7.50
X_coordinate= 99.250
Y_coordinate= -15.2500
Z_coordinate= -7.50
X_coordinate= 99.250
Y_coordinate= -15.2500
Z_coordinate= -12.50
X_coordinate= 99.250
Y_coordinate= 14.7500
Z_coordinate= -12.50

X_coordinate= 99.50
Y_coordinate= 14.50
Z_coordinate= -7.50
X_coordinate= 99.50
Y_coordinate= -15.500
Z_coordinate= -7.50
X_coordinate= 99.50
Y_coordinate= -15.500
Z_coordinate= -12.50
X_coordinate= 99.50
Y_coordinate= 14.50
Z_coordinate= -12.50

X_coordinate= 100.0
Y_coordinate= 14.00
Z_coordinate= -7.50
X_coordinate= 100.0
Y_coordinate= -15.750
Z_coordinate= -7.50
X_coordinate= 100.0
Y_coordinate= -15.750
Z_coordinate= -12.50
X_coordinate= 100.0
Y_coordinate= 14.00
Z_coordinate= -12.50

X_coordinate= 110.0
Y_coordinate= 14.00
Z_coordinate= -7.50
X_coordinate= 110.0
Y_coordinate= -15.750
Z_coordinate= -7.50
X_coordinate= 110.0
Y_coordinate= -15.750
Z_coordinate= -12.50
X_coordinate= 110.0
Y_coordinate= 14.00
Z_coordinate= -12.50



X_coordinate= 110.0
Y_coordinate= 14.00
Z_coordinate= -7.50
X_coordinate= 110.0
Y_coordinate= -15.750
Z_coordinate= -7.50
X_coordinate= 120.0
Y_coordinate= -15.750
Z_coordinate= -12.50
X_coordinate= 120.0
Y_coordinate= 14.00
Z_coordinate= -12.50

X_coordinate= 110.0
Y_coordinate= 14.00
Z_coordinate= -5.50
X_coordinate= 110.0
Y_coordinate= -15.750
Z_coordinate= -5.50
X_coordinate= 120.0
Y_coordinate= -15.750
Z_coordinate= -7.50
X_coordinate= 120.0
Y_coordinate= 14.00
Z_coordinate= -7.50

X_coordinate= 80.0
Y_coordinate= 14.00
Z_coordinate= -5.50
X_coordinate= 80.0
Y_coordinate= -15.750
Z_coordinate= -5.50
X_coordinate= 80.0
Y_coordinate= -15.750
Z_coordinate= -7.50
X_coordinate= 80.0
Y_coordinate= 14.00
Z_coordinate= -7.50

X_coordinate= 70.0
Y_coordinate= 14.00
Z_coordinate= -5.50
X_coordinate= 70.0
Y_coordinate= -15.750
Z_coordinate= -5.50
X_coordinate= 80.0
Y_coordinate= -15.750
Z_coordinate= -5.50
X_coordinate= 80.0
Y_coordinate= 14.00
Z_coordinate= -5.50

X_coordinate= 70.0
Y_coordinate= 14.00
Z_coordinate= -2.50
X_coordinate= 70.0
Y_coordinate= -15.750
Z_coordinate= -2.50
X_coordinate= 80.0
Y_coordinate= -15.750
Z_coordinate= -2.50
X_coordinate= 80.0
Y_coordinate= 14.00
Z_coordinate= -2.50

Here is the image

Желаемое изменение направления Изображение 2

Немного более детальное представление о желаемом изменении направления 3.

Ответы [ 2 ]

3 голосов
/ 27 апреля 2019

Интерполяция через точки в вашем файле данных не может быть легко выполнена. Я бы рекомендовал design B-сплайновые кривые для интерполяции точек между плоскостью 1 и 2 и плоскостью 6 и 7. Пространства между плоскостью 2 и плоскостью 6, плоскостью 7 и плоскостью 10 кажутся довольно линейными:

clear
filename = 'datafile.txt';
A = importdata(filename);

vertices = A.data(2:end);
vertices = reshape(vertices, 12, [])';

vx = vertices(:, 1:3:10);
vy = vertices(:,2:3:11);
vz = vertices(:,3:3:12);
figure; patch(vx',vy',vz',1); axis equal;

enter image description here

Нет простого способа выполнить такую ​​интерполяцию, потому что вы хотите обеспечить как минимум непрерывность С1 вдоль кривой, чтобы избежать какого-либо острого края. Кривая B-сплайна здесь может быть полезна, но если вы не знакомы с ней, вам будет сложно ее запрограммировать. К счастью, я работал над проектом, требующим подгонки поверхности и кривой, и у меня есть код:

function [x,y,z] = bspline(u, ctrlp, k, knots)    
U = bspbasis(u, numel(ctrlp(:,1)), k, knots);

x=U*ctrlp(:,1);
y=U*ctrlp(:,2);
z=U*ctrlp(:,3);
end

function U = bspbasis(u, nctrlp, K, knots)
nu = numel(u);
umax = max(u);
index = 1:nctrlp;

% preallocating variables
U = zeros(nu,nctrlp);
N = zeros(nctrlp+1,K);

% Calculate the denominators for basis functions (k>2). -may be useful when
% the size of point data is substantial, so the calculation is not repeated.
d1 = zeros(nctrlp,K);
d2 = d1;
for m=2:K
    d1(:,m) = knots(index+m-1) - knots(index);
    d2(:,m) = knots(index+m) - knots(index+1);
end

knots = knots(:); 
knots1 = knots(1:nctrlp+1); 
knots2 = knots(2:nctrlp+2);
knotSize = size(knots1);
knotc1 = knots(nctrlp+1); 
knotc2 = knots(nctrlp+2);

for ui = 1:nu
    % k = 1
    u_ = u(ui)*ones(knotSize);
    NA = u_>=knots1 & u_<knots2;
    if u(ui) == umax && knotc1 == umax && knotc2 == umax
        NA(1:nctrlp+1) = 0;
        NA(end-1,1) = 1;
    end
    N(:,1) = NA;
    % k > 2
    for k = 2:K
        p1 = (u(ui) - knots(index)) ./ d1(:,k) .* N(index,k-1);
        p1(isnan(p1)) = 0;
        p2 = (knots(index+k) - u(ui)) ./ d2(:,k) .* N(index+1,k-1);
        p2(isnan(p2)) = 0;
        N(index,k) = p1 + p2;
    end
    U(ui,:)=N(1:end-1,k);
end
end

Если вы хотите понять приведенный выше код, вы можете прочитать B-spline Wikipedia . На Youtube также есть множество учебных пособий и интерактивных инструментов, таких как этот .


Код ниже соответствует B-сплайну 3-го порядка для каждого из четырех наборов угловых вершин.

u = linspace(0,1,500);
k = 3;

for i = 1:4
    ishift = (i-1) * 3 + 1;
    p = vertices(:,ishift:ishift+2);

    ctrlp = [p(1,:); [0 0 0]; p(2:3,:); p(5:6,:); zeros(2,3); p(7,:); p(8,:)];
    ctrlp(2,:) = 5*ctrlp(3,:) - 4*ctrlp(4,:);
    ctrlp(7,:) = 2*ctrlp(6,:) - ctrlp(5,:); 
    ctrlp(8,:) = 4*ctrlp(9,:) - 3*ctrlp(10,:);
    knots = getknots(ctrlp, k);
    [x_,y_,z_] = bspline(u, ctrlp, k, knots);
    x(:,i) = [p(:,1); x_];
    y(:,i) = [p(:,2); y_];
    z(:,i) = [p(:,3); z_];

    [~,I] = sort(x(:,i));
    x(:,i) = x(I,i);
    y(:,i) = y(I,i);
    z(:,i) = z(I,i);
end
c = repmat(1:numel(x)/4,4,1)';
xx=[x;flipud(x(:,[2,3,4,1]))];
yy=[y;flipud(y(:,[2,3,4,1]))];
zz=[z;flipud(z(:,[2,3,4,1]))];
cc=[c;flipud(c)];
figure; patch(xx,yy,zz,cc);

В результате получается гладкая поверхность:

enter image description here


Я хотел объяснить вам, как работает код, но после того, как я боролся с ним в течение часа, я сдался ... Вместо этого я предоставлю краткое изложение некоторых ключевых моментов ниже.

В сводке используются следующие обозначения: C1, C2, ..., C10 - контрольные точки кривой B-сплайна, а V1, V2, ..., V10 - вершины, используемые для вычисления B- сплайн-кривая. На рисунке ниже показаны контрольные точки и вершины, использованные для расчета первой кривой B-сплайнов.

enter image description here

  1. Количество значений в u определяет количество точек на конечной кривой.
  2. B-сплайновая кривая 3-го порядка достаточна.
  3. Кривая должна проходить через V1, V2, ..., V10.
  4. Чтобы пройти через V1 и V10, кривая B-сплайна должна быть фиксированного типа. Следовательно, C1 = V1 и C10 = V10.
  5. Чтобы пройти через V2, C2 должен быть на линии, проходящей через V2, а V3 и C3 должны равняться V2. Следовательно, C2 - V2 = d*(V2-V3) и C3 = V2.
  6. Чтобы пройти через V3, V4 и V5, должно быть выполнено следующее условие: C3 = V2; C4 = V3; C5 = V5; C6 = V6.
  7. Чтобы пройти через V6, C7 должен быть на линии, проходящей через V5 и V6, вместе с C6, равным V6. Следовательно, C7-V6 = d*(V6-V5).
  8. Чтобы пройти через V7, C8 должен быть на линии, проходящей через V7 и V8, а C9 должен равняться V7. Следовательно, C8-V7 = d*(V7-V8); C9 = V7;.
  9. Поскольку C10 = V10 и C9 = V7, кривая проходит через V8 и V9.
  10. Наконец, узлы могут быть либо однородными, либо оцениваться по длине хорды между контрольными точками.

Обновление: getknots

Функция getknots:

function knots = getknots(ctrlp, k)
d = sqrt(sum(diff(ctrlp).^2, 2));
ds = cumsum(d)./sum(d);
knots = [zeros(k,1); ds(k-1:end); ones(k,1)];
end

Все в одном коде для метода bspline:

clear
filename = 'datafile.txt';
A = importdata(filename);

vertices = A.data(2:end);
vertices = reshape(vertices, 12, [])';

u = linspace(0,1,500);
k = 3;

for i = 1:4
    ishift = (i-1) * 3 + 1;
    p = vertices(:,ishift:ishift+2);

    ctrlp = [p(1,:); [0 0 0]; p(2:3,:); p(5:6,:); zeros(2,3); p(7,:); p(8,:)];
    ctrlp(2,:) = 5*ctrlp(3,:) - 4*ctrlp(4,:);
    ctrlp(7,:) = 2*ctrlp(6,:) - ctrlp(5,:); 
    ctrlp(8,:) = 4*ctrlp(9,:) - 3*ctrlp(10,:);
    knots = getknots(ctrlp, k);
    [x_,y_,z_] = bspline(u, ctrlp, k, knots);
    x(:,i) = [p(:,1); x_];
    y(:,i) = [p(:,2); y_];
    z(:,i) = [p(:,3); z_];

    [~,I] = sort(x(:,i));
    x(:,i) = x(I,i);
    y(:,i) = y(I,i);
    z(:,i) = z(I,i);
end
c = repmat(1:numel(x)/4,4,1)';
xx=[x;flipud(x(:,[2,3,4,1]))];
yy=[y;flipud(y(:,[2,3,4,1]))];
zz=[z;flipud(z(:,[2,3,4,1]))];
cc=[c;flipud(c)];
figure; patch(xx,yy,zz,cc);

function [x,y,z] = bspline(u, ctrlp, k, knots)    
U = bspbasis(u, numel(ctrlp(:,1)), k, knots);

x=U*ctrlp(:,1);
y=U*ctrlp(:,2);
z=U*ctrlp(:,3);
end

function U = bspbasis(u, nctrlp, K, knots)
nu = numel(u);
umax = max(u);
index = 1:nctrlp;

% preallocating variables
U = zeros(nu,nctrlp);
N = zeros(nctrlp+1,K);

% Calculate the denominators for basis functions (k>2). -may be useful when
% the size of point data is substantial, so the calculation is not repeated.
d1 = zeros(nctrlp,K);
d2 = d1;
for m=2:K
    d1(:,m) = knots(index+m-1) - knots(index);
    d2(:,m) = knots(index+m) - knots(index+1);
end

knots = knots(:); 
knots1 = knots(1:nctrlp+1); 
knots2 = knots(2:nctrlp+2);
knotSize = size(knots1);
knotc1 = knots(nctrlp+1); 
knotc2 = knots(nctrlp+2);

for ui = 1:nu
    % k = 1
    u_ = u(ui)*ones(knotSize);
    NA = u_>=knots1 & u_<knots2;
    if u(ui) == umax && knotc1 == umax && knotc2 == umax
        NA(1:nctrlp+1) = 0;
        NA(end-1,1) = 1;
    end
    N(:,1) = NA;
    % k > 2
    for k = 2:K
        p1 = (u(ui) - knots(index)) ./ d1(:,k) .* N(index,k-1);
        p1(isnan(p1)) = 0;
        p2 = (knots(index+k) - u(ui)) ./ d2(:,k) .* N(index+1,k-1);
        p2(isnan(p2)) = 0;
        N(index,k) = p1 + p2;
    end
    U(ui,:)=N(1:end-1,k);
end
end

function knots = getknots(ctrlp, k)
d = sqrt(sum(diff(ctrlp).^2, 2));
ds = cumsum(d)./sum(d);
knots = [zeros(k,1); ds(k-1:end); ones(k,1)];
end
2 голосов
/ 27 апреля 2019

Этот ответ был первоначально опубликован в моем первом ответе, но первый ответ становится слишком многолюдным и его трудно отследить. Поэтому я сейчас отделяю этот ответ от второго ответа. Это также альтернативный и другой подход к решению вопроса.


Если у вас есть набор точек с хорошей плотностью, вы можете использовать встроенную в MATLAB функцию интерполяции interp1.

Во-первых, вам необходимо параметризовать точки. Длина аккорда здесь достаточно хороша:

clear
filename = 'datafile.txt';
A = importdata(filename);

vertices = A.data(2:end);
vertices = reshape(vertices, 12, [])';

for i = 1:4
    % take a set of vertices that will form an edge of the lofted body.
    ishift = (i-1) * 3 + 1;
    p = vertices(:,ishift:ishift+2);

    %calculate distance between two neighbouring points.
    dp = sqrt(sum(diff(p).^2,2)); 
    u = [0; cumsum(dp)/sum(dp)];

    % do more later
end

Затем сгенерируйте новые наборы значений u для расчета подгоночных точек:

unew = unique([u; linspace(0,1,500)']);

А затем соедините точки с помощью interp1:

pnew = interp1(u, p, unew, 'spline');
figure; hold on;
plot3(pnew(:,1), pnew(:,2), pnew(:,3));
plot3(p(:,1), p(:,2), p(:,3),'o');
axis equal;

Подводя итог, цикл for теперь:

for i = 1:4
    ishift = (i-1) * 3 + 1;
    p = vertices(:,ishift:ishift+2);

    dp = sqrt(sum(diff(p).^2,2));
    u = [0; cumsum(dp)/sum(dp)];
    unew = unique([u; linspace(0,1,500)']);
    pnew = interp1(u, p, unew, 'spline');
    x(:,i) = pnew(:,1);
    y(:,i) = pnew(:,2);
    z(:,i) = pnew(:,3);
end

Однако, поскольку у вас есть только 10 очков за каждое ребро, вы получите следующие результаты:

enter image description here

Взяв одну из кривых ребер:

enter image description here

Огромная кривизна между V1 и V2 явно нежелательна. Однако, поскольку разница между V1 и V2 должна быть только линейной, вы можете искусственно добавлять точки между ними. Например, добавив две точки:

p = vertices(:,ishift:ishift+2);
insertedP = (p(2,:) - p(1,:)).*(0.33; 0.66);
insertedP = insertedP + repmat(p(1,:),size(insertedP,1),1);
p = [p(1,:); insertedP; p(2:end,:)];

И затем, выполнив те же вычисления, вы получите:

enter image description here

Если вы добавите еще больше:

insertedP = (p(2,:) - p(1,:)).*(0.02:0.02:0.98)';

enter image description here

Но это не идеально, так как вы получите видимое колебание на V2, которое неизбежно из-за непрерывности:

enter image description here

Однако это можно контролировать, удаляя или добавляя точки, близкие к V2:

insertedP = (p(2,:) - p(1,:)).*(0.02:0.02:0.68)';

enter image description here

Обратите внимание, что вы также получаете такие колебания с помощью метода B-сплайна, опубликованного в другом ответе, но это более управляемо и предсказуемо, перемещая 2-ю контрольную точку ближе или дальше к V2.

Поднятое тело, сгенерированное методом interp1, выглядит так:

enter image description here

Подводя итог, полный код для воспроизведения вышеуказанного изображения приведен ниже:

clear
filename = 'datafile.txt';
A = importdata(filename);

vertices = A.data(2:end);
vertices = reshape(vertices, 12, [])';

for i = 1:4
    ishift = (i-1) * 3 + 1;
    p = vertices(:,ishift:ishift+2);
    insertedP = (p(2,:) - p(1,:)).*(0.02:0.02:0.98)';
    insertedP = insertedP + repmat(p(1,:),size(insertedP,1),1);
    p = [p(1,:); insertedP; p(2:end,:)];

    dp = sqrt(sum(diff(p).^2,2));
    u = [0; cumsum(dp)/sum(dp)];
    unew = unique([u; linspace(0,1,500)']);
    pnew = interp1(u, p, unew, 'spline');
    x(:,i) = pnew(:,1);
    y(:,i) = pnew(:,2);
    z(:,i) = pnew(:,3);
end
c = repmat(1:numel(x)/4,4,1)';
xx=[x;flipud(x(:,[2,3,4,1]))];
yy=[y;flipud(y(:,[2,3,4,1]))];
zz=[z;flipud(z(:,[2,3,4,1]))];
cc=[c;flipud(c)];
figure; patch(xx,yy,zz,cc);

Если у вас есть больше точек для ограничения интерполяции, вы можете удалить строки, включающие insertP:

insertedP = (p(2,:) - p(1,:)).*(0.02:0.02:0.98)';
insertedP = insertedP + repmat(p(1,:),size(insertedP,1),1);
p = [p(1,:); insertedP; p(2:end,:)];
...