将一条3D线绘制到数组中

3
我正在寻找一种解决方案,使用Python“绘制”一个3D线(填充的圆柱体)到一个3D数组中,就像skimage.draw.line函数对2个数组所做的操作一样。
该线应具有起点(x1, y1, z1)、终点(x2, y2, z2)和半径R
我看过2D线的例子,但我无法修改它以使其在3D情况下工作。
我想过在数组中绘制连续的椭圆,但我不知道如何计算两个轴和旋转角度。
也许有一个更简单的方法来解决这个问题?

1
请添加一些规格,例如您的数组是如何定义的。这两个点不足以提供更多信息,您还需要线条的厚度或半径,请参考LED Cube draw sphereGLSL volumetric back raytracer。您想要完整的体积还是只有表面?顺便说一句,我确定我最近在这里看到了“确切”的相同问题(可能是1-2个月前),并且已经有了可行的解决方案,正在寻找更好的性能,因此请尝试找到它(不确定标题和语言是什么)。 - Spektre
谢谢提供的链接,我会去看一下!是的,你需要指定半径 R。此外,体积应该被填满。我试着去找你提到的问题,但是找不到。 - user3637203
不,那不是那个。我也找不到它(否则我会链接它的),它是使用Bresenham算法的3D模型,并且只想要表面体素,并且关注孔洞问题(这不是完全填充体积的情况)。我为你添加了一个基于我的体积后向光线跟踪器的答案...我现在只是为了好玩而破解它... - Spektre
2个回答

2
假设这个GLSL体积后向光线跟踪器作为起点。要创建像这样的填充3D线条:

overview

你需要:
  1. endpoints as spheres

    see the add_sphere in the link above.

  2. discs cut at the endpoints

    for that we need U,V basis vectors (perpendicular vectors to each other and to line itself). With those we can simply use any 2D circle pixels acquisition and convert them to 3D voxel positions with ease. So if u,v are coordinates in some 2D circle centered at (0,0) then:

    x = x0 + u*U.x + v*V.x
    y = y0 + u*U.y + v*V.y
    z = z0 + u*U.z + v*V.z
    

    (x,y,z) are corresponding to 3D circle voxel coordinate with center (x0,y0,z0). For more info see my C++ glCircle3D implementation.

  3. line body

    As wee got all the voxel positions in the disc around x0,y0,z0 endpoint of the line just cast a line from each of it with the same slope as line (x0,y0,z0),(x1,y1,z1) which are the endpoints of your line.

当我在C++中将它们组合在一起时(抱歉,我不会用Python编程),我得到了以下内容:

void volume::add_line(int x0,int y0,int z0,int x1,int y1,int z1,int r,GLuint col)
    {
    if (!_init) return;
    int i,n,x,y,z,cx,cy,cz,dx,dy,dz,kx,ky,kz;
    // endpoints are (half)spheres
    add_sphere(x0,y0,z0,r,col);
    add_sphere(x1,y1,z1,r,col);
    // DDA constants
    kx=0; dx=x1-x0; if (dx>0) kx=+1; if (dx<0) { kx=-1; dx=-dx; } dx++;           n=dx;
    ky=0; dy=y1-y0; if (dy>0) ky=+1; if (dy<0) { ky=-1; dy=-dy; } dy++; if (n<dy) n=dy;
    kz=0; dz=z1-z0; if (dz>0) kz=+1; if (dz<0) { kz=-1; dz=-dz; } dz++; if (n<dz) n=dz;
    // basis vectors
    double U[3],V[3],N[3]={x1-x0,y1-y0,z1-z0},u,v,rr=r*r;
    vector_one(N,N); // unit vector
    vector_ld(U,1.0,0.0,0.0); if (fabs(vector_mul(U,N))>=0.75) vector_ld(U,0.0,1.0,0.0); // |dot(U,N)|<0.75 means (1.0,0.0,0.0) is nearly parallel to N so chose (0.0,1.0,0.0) instead
    vector_mul(U,U,N); // U = U x N
    vector_mul(V,U,N); // V = U x N
    vector_one(U,U);   // U /= |U|
    vector_one(V,V);   // V /= |V|
    // disc
    for (u=-r;u<=+r;u++)
     for (v=-r;v<=+r;v++)
      if (u*u+v*v<=rr)
        {
        x=x0+double((u*U[0])+(v*V[0]));
        y=y0+double((u*U[1])+(v*V[1]));
        z=z0+double((u*U[2])+(v*V[2]));
        // DDA line
        for (cx=cy=cz=n,i=0;i<n;i++)
            {
            if ((x>=0)&&(x<size)&&(y>=0)&&(y<size)&&(z>=0)&&(z<size)) data[z][y][x]=col;
            cx-=dx; if (cx<=0) { cx+=n; x+=kx; }
            cy-=dy; if (cy<=0) { cy+=n; y+=ky; }
            cz-=dz; if (cz<=0) { cz+=n; z+=kz; }
            }
        }
    }
vector_xxx 函数仅涉及我的 3D 向量数学,只使用点积、叉积和单位大小标准化,易于实现。您可以在此处查看它们: 还有一些可以改进的地方,例如球体可以是半球体,并且它们的生成可以与圆盘合并...因为法线和未偏移的 3D 球面坐标之间的点积要么为正/零/负,从而区分终点半球体和圆盘...这也将完全消除对 U,V 的需求。
此外,根据所使用的硬件和环境,还可以有更快的方法,例如基于距离线的(基于距离的 BBOX 填充)分析方法,如果快速向量数学结合大规模并行处理(如在 GPU 上)。
在我的引擎中进行了一些调整(添加缩放并处理了一些精度问题),我得到了以下结果: 128x128x128 的体积在此处初始化:

objects

// init volume raytracer
vol.gl_init();
vol.beg();
int r,a,b,c;
r=10.0; a=r+1; b=vol.size-r-2; c=vol.size>>1;
                             //BBGGRR
vol.add_line(a,a,a,b,a,a,r,0x00FF2020);
vol.add_line(a,b,a,b,b,a,r,0x00FF2020);
vol.add_line(a,a,a,a,b,a,r,0x00FF2020);
vol.add_line(b,a,a,b,b,a,r,0x00FF2020);

vol.add_line(a,a,b,b,a,b,r,0x00FF2020);
vol.add_line(a,b,b,b,b,b,r,0x00FF2020);
vol.add_line(a,a,b,a,b,b,r,0x00FF2020);
vol.add_line(b,a,b,b,b,b,r,0x00FF2020);

vol.add_line(a,a,a,a,a,b,r,0x00FF2020);
vol.add_line(a,b,a,a,b,b,r,0x00FF2020);
vol.add_line(b,a,a,b,a,b,r,0x00FF2020);
vol.add_line(b,b,a,b,b,b,r,0x00FF2020);

vol.add_sphere(c,c,c,c>>1,0x00FF8040);
vol.add_sphere(a,c,c,r,0x004080FF);
vol.add_sphere(b,c,c,r,0x0080FF40);
vol.add_sphere(c,a,c,r,0x00FF4080);
vol.add_sphere(c,b,c,r,0x00AAAAAA);
vol.add_box(c,c,a,r,r,r,0x0060FF60);
vol.add_box(c,c,b,r,r,r,0x00FF2020);

vol.end();

非常好的解决方案!谢谢! - user3637203
只是一个快速的问题:我正在将代码移植到Python,你正在使用浮点数 x,y,z 来索引 data。C++ 的行为是什么?向上取整、向下取整还是标准四舍五入? - user3637203
1
@user3637203,x,y,z 是整数,唯一的 float/doublesu,v,U[3],V[3],N[3],而且 int=float 会将结果向下取整。 - Spektre
1
如果我理解代码正确的话,vector_mul() 给出的是叉积。但是在一行中,你调用了 fabs(vector_mul(U,N))>=0.75,所以你是在将向量与标量进行比较吗?还是说这是 C++ 的魔法,vector_mul() 有时会给出点积,有时会给出叉积? - user3637203
@user3637203 double vector_mul(double *a,double *b) 返回点积 (a.b),而 void vector_mul(double c,double a,double b) 则是叉积 c=a x b。我在代码中添加了一些注释以使其更有意义。 - Spektre
显示剩余2条评论

1

我最终成功使用skimage.draw.ellipse在Python中实现了这个功能:

import numpy as np
from numpy.linalg import norm
import skimage.draw

c = lambda *x: np.array(x, dtype=float)
vector_angle = lambda V, U: np.arccos(norm(np.dot(V, U)) / (norm(V) + norm(U)))

r = 10 # radius of cylinder
C0 = c(10, 10, 10) # first (x,y,z) point of cylinder
C1 = c(99, 90, 15) # second (x,y,z) point of cylinder

C = C1 - C0

X, Y, Z = np.eye(3)

theta = vector_angle(Z, C)
print('theta={} deg'.format(theta / np.pi * 180))

minor_axis = r
major_axis = r / np.cos(theta)
print('major_axis', major_axis)

alpha = vector_angle(X, C0 + C)
print('alpha={} deg'.format(alpha / np.pi * 180))

data = np.zeros([100, 100, 100])
nz, ny, nx = data.shape

for z in range(nz):
    lam = - (C0[2] - z)/C[2]
    P = C0 + C * lam
    y, x = skimage.draw.ellipse(P[1], P[0], major_axis, minor_axis, shape=(ny, nx), rotation=alpha)
    data[z, y, x] = 1

这是否也可以不使用圆柱体,只是在两个点之间有一条3D线?最好的问候! - Varlor
如果你从a到b再从b到c画一条线,但是中间有一个皱褶,那么边缘会怎样呢? - Varlor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接