3D列印技術之切片引擎(3)

玉名卍發表於2015-03-02

【此係列文章基於熔融沉積( fused depostion modeling, FDM )成形工藝

從這一篇文章開始,我講一下實體切片方面的一些技術。

切片引擎,實體部分大致包括:

提取邊界向量——>新增多邊——>生成填充向量集合。

其中生成填充向量集合是整個切片引擎技術的核心,因為衡量一款引擎的好壞的四個核心要素——穩固性;與原始模型的相似度;用了多少材料;列印快慢,都是主要取決於填充向量的生成技術。目前生成填充向量的演算法還有很多未知的問題有待攻克,對於這一塊技術我將在最後與大家討論,在這一篇文章裡,我主要的說一下提取邊界向量的技術。

提取邊界向量,就是在對模型分層的時候,獲得模型與某一層(z平面)相交的向量集合,並且以特定順序(一般規定順著Z軸負方向看為正向)首尾相接。

其中難點主要是要所有的層都以特定順序首尾相接,看下圖:


這是某一模型在skeinforge下生成的某一層的向量集合。看它的邊界向量,就是如上所述,按照某一特定順序,首尾相接。

求三角面片和z平面的交線是高中解析幾何的知識,這裡只強調一點(很重要的一點):當切到某個三角面片與z軸垂直的時候(也就是三角面片與z平面重合的情況),該如何辦?尤其是有些模型同一個z平面上會有很多個三角面片。用演算法也可以解決,但是很麻煩,而且增加了很多邏輯上不好的程式碼,降低了程式碼的可讀性。這時候一個較好的解決方案就是避開,因為這種情況畢竟是極少數,也就是說機器在對模型分層的時候,恰巧切到了這一含有三角面片的層。我們需要假設模型是光滑的,即三角面片之間是近似於光滑過渡的(趨近於光滑流形),也就是說,每個互連的三角面片的夾角儘可能的接近於180度,這是一個良好的3D模型所必須具備的。應用微分學原理,可以在這一層沿z軸方向的上面或者下面很小的距離取一層來近似的代替它。這樣這個棘手的問題就得到了較好的解決。

首尾相接這一塊只需略懂資料結構就非常容易做到的。在這裡就不贅述了。

接下來我就重點說一下如何讓所有的交線按照某一特定方向來生成邊界向量

演算法步驟如下:

第一步,求出來的三角面片和z平面交線段的兩個端點:beginPoint和endPoint,把它假設成為我們想要的符合特定順序的向量:phasor。開始節點:beginPoint,結束節點:endPoint。

第二步,就是判斷該向量是否符合我們的要求的,如果不符合就對它做反轉。

步驟如下:

1,做beginPoint和endPoint的差vectorDiff。

2,對vectorDiff和該三角面片的法向量normal做叉積cross。

3,beginPoint和cross相加得到vectorAdd。

4,做三個向量(1,1,z),(1,0,z),(0,1,z).這三個向量的第三個元素都是z,要保證線性無關(三個線性無關的向量確定一個平面)。

5,對這三個向量以及vectorAdd做四點行列式,如果行列式的值小於0,反轉,否則不反轉。

可能對於初學者,四點行列式有些難以理解,對此,請參看我的另一篇博文: Devillers & Guigue演算法

為什麼要這樣做,有一點點演算法功底的同學容易理解,這樣做的目的主要是充分利用法向量來判斷二維向量的合理方向,因為對傳統的多邊形面片離散化的3D模型,法向量唯一標識3D模型面的方向,要使得提取的邊界向量方向規範就必須充分利用法向量資訊。

至於我所做的三個向量,是為了標定z平面,只需找到這個平面三個不相關的向量就可以了。

講這麼多,上述演算法應該就不難理解了。

下面呈上該演算法的程式碼。

//向量
struct Phasor
{
	float3 beginPoint,endPoint;

	int status;
	
	int material;

	int tri_index;

	void reversal()
	{
		float3 temp;

		memcpy(temp,beginPoint,sizeof(float)*3);

		memcpy(beginPoint,endPoint,sizeof(float)*3);

		memcpy(endPoint,temp,sizeof(float)*3);
	}

	void copy(Phasor *p)
	{
		memcpy(beginPoint,p->beginPoint,sizeof(float)*3);

		memcpy(endPoint,p->endPoint,sizeof(float)*3);

		status=p->status;

		material=p->material;

		tri_index=p->tri_index;
	}
};

void get_vector_diff( float3& aimV, const float3 a, const float3 b )
{
    aimV[0] = b[0] - a[0];
	
    aimV[1] = b[1] - a[1];
	
    aimV[2] = b[2] - a[2];
}

void cross_product(float3 &result,float3 a, float3 b)
{
	result[0] = a[1]*b[2] - a[2]*b[1];
	result[1] = a[2]*b[0] - a[0]*b[2];
	result[2] = a[0]*b[1] - a[1]*b[0];
}


void get_vector_sum( float3& aimV, const float3 a, const float3 b )
{
    aimV[0] = a[0] + b[0];
	
    aimV[1] = a[1] + b[1];
	
    aimV[2] = a[2] + b[2];
}

//獲得四點的行列式
float get_vector4_det( float3 v1, float3 v2, float3 v3, float3 v4 )
{
	
    float a[3][3];
    for ( int i = 0; i != 3; ++i )
    {
        a[0][i] = v1[i] - v4[i];
        a[1][i] = v2[i] - v4[i];
        a[2][i] = v3[i] - v4[i];
    }
	
    return a[0][0] * a[1][1] * a[2][2] + 
		a[0][1] * a[1][2] * a[2][0] + 
		a[0][2] * a[1][0] * a[2][1] - 
		a[0][2] * a[1][1] * a[2][0] - 
		a[0][1] * a[1][0] * a[2][2] - 
		a[0][0] * a[1][2] * a[2][1];
}




/// <summary>   
/// 如果邊界向量不符合要求,改變向量方向
/// </summary>     
/// <param name="phasor">向量</param> 
/// <param name="normal">法向</param>
/// <param name="z">z座標</param>
void STLDelamination::rectifyDirection(Phasor *phasor,float3 normal,float z)
{
	float3 cross;

	float3 vectorDiff;

	float3 vectorAdd;

	float3 a,b,c,temp;

	a[0]=1;
	a[1]=1;
	a[2]=z;

	b[0]=1;
	b[1]=0;
	b[2]=z;

	c[0]=0;
	c[1]=1;
	c[2]=z;

	get_vector_diff(vectorDiff,phasor->beginPoint,phasor->endPoint);

	cross_product(cross,normal,vectorDiff);

	get_vector_sum(vectorAdd,phasor->beginPoint,cross);


	if(get_vector4_det(a, b, c, vectorAdd )<0)
	{
		memcpy(temp,phasor->beginPoint,sizeof(float)*3);
		
		memcpy(phasor->beginPoint,phasor->endPoint,sizeof(float)*3);
		
		memcpy(phasor->endPoint,temp,sizeof(float)*3);
	}
}






相關文章