设有按逆时针方向设置的一个三角形,
var triangle = [ -0.5, -0.5, 0.0, // v0 0.5, -0.5, 0.0, // v1 0.0, 0.5, 0.0 // v2 ];
先将这三个顶点转换为Vector:
var v0 = new J3DIVector3(triangle[0], triangle[1], triangle[2]); var v1 = new J3DIVector3(triangle[3], triangle[4], triangle[5]); var v2 = new J3DIVector3(triangle[6], triangle[7], triangle[8]);
之后,两两进行叉乘,以得到其法线。
// counter-clock-wise cross product var normal = v0 * v1; // (0.0, 0.0, 0.5) var normal = v1 * v2; // (0.0, 0.0, 0.25) var normal = v2 * v0; // (0.0, 0.0, 0.25) // clock-wise cross product var normal = v1 * v0; // (0.0, 0.0, -0.5) var normal = v2 * v1; // (0.0, 0.0, -0.25) var normal = v0 * v2; // (0.0, 0.0, -0.25)
由于三角形的正面朝向用户,因此,正确的法线方向也应朝向用户。而上面的六种结果中,只有前面三个结果是正确的。
可见,在按逆时针设置的三角形中,只要按逆时针取出任意两点进行叉乘,就可得出正确的平面法线方向。
用这种方式求三角形平面的法线正确吗?也对,也不对。我们无意中犯了一个错误。在上面定义三角形顶点的代码中,由于三个顶点的Z轴坐标均为0,导致这个三角形平面垂直于Z轴,从而犯了一个概念混淆的错误:直接将顶点转换为Vector了。三角形的顶点不是Vector!
先将V2脱离Z轴。
再将各顶点与坐标系原点连接。
此图可清晰地看出,顶点是原点到各顶点的距离,也即顶点在坐标系中的位置。而我们要求出与三角形平面垂直的法线,应将两条相交的边进行叉乘,才能得到正确的结果。也即,三条边才是求得法线的矢量。
修改代码:
var point3 = function(x, y, z) { return {x:x, y:y, z:z}; }; var pt0 = point3(-0.5, -0.5, -0.0); var pt1 = point3( 0.5, -0.5, -0.0); var pt2 = point3( 0.0, 0.5, -0.5); var triangle = [ pt0.x, pt0.y, pt0.z, pt1.x, pt1.y, pt1.z, pt2.x, pt2.y, pt2.z ];
我们取相交于pt0的两条边作为矢量。即pt0到pt1, pt0到pt2的的两条边。
根据矢量运算规律,原点到pt0的矢量减去原点到pt1的矢量,可以得到pt0到pt1的矢量。同理,原点到pt0的矢量减去原点到pt2的矢量,可以得到pt0到pt2的矢量。
var v0 = new J3DIVector3(pt0.x, pt0.y, pt0.z); var v1 = new J3DIVector3(pt1.x, pt1.y, pt1.z); var v2 = new J3DIVector3(pt2.x, pt2.y, pt2.z); var v01 = sub(v0, v1); var v02 = sub(v0, v2); function sub(vector1, vector2) { return new J3DIVector3(vector1[0] - vector2[0], vector1[1] - vector2[1], vector1[2] - vector2[2]); }
之后,根据这两个矢量求得法线,并归一化。
var normal = getNoramlFromVector(v01, v02); normal.divide(normal.vectorLength()); function getNoramlFromVector(v1, v2) { var normal = new J3DIVector3(v1[0], v1[1], v1[2]); normal.cross(v2); return normal; }
将原点到三角形各顶点的连线去掉,可清楚地看到这条法线垂直于三角形平面的效果。