已知弹簧的起点和终点坐标值,在OpenGL中绘制该弹簧。本篇介绍的弹簧分两种,分别是无粗细的(使用LineStrip绘制)和有粗细的(使用QuadStrip绘制)。程序是在C#和OpenTK环境下编译的。
1. 弹簧的角度
由于弹簧的位置是由起点和终点两个坐标定义的,所以需要考虑弹簧的角度问题。
程序中直接使用的OpenTK下的函数CalculateAngle和CreateFromAxisAngle。关于这两个函数的数学公式推导,参见我的另一篇文章:根据旋转前后的向量值求旋转矩阵
首先绘制沿Z轴方向的弹簧,然后根据弹簧的两端点求出旋转矩阵,最后设定该弹簧的方向和位置。
2. 无粗细的弹簧
代码:
/// <summary> /// 绘制线弹簧 /// </summary> /// <param name="ptStart">弹簧的起点</param> /// <param name="ptEnd">弹簧的终点</param> /// <param name="Radius">弹簧的半径</param> /// <param name="Coils">弹簧的圈数</param> /// <param name="Rings">弹簧每圈的段数</param> void DrawSpring(Vector3d ptStart, Vector3d ptEnd, double Radius = 50, double Coils = 12, double Rings = 50) { double springHeight; double ringDelta = 2.0 * Math.PI / Rings; double ringHeight; double ringAngle; double z; Vector3d ptOldFrame, ptNewFrame; Vector3d rotationAxis; Matrix4d rotationMatrix; double rotationAngle; ptOldFrame = new Vector3d(0, 0, 1); ptNewFrame = ptEnd - ptStart; springHeight = Math.Sqrt(ptNewFrame.X * ptNewFrame.X + ptNewFrame.Y * ptNewFrame.Y + ptNewFrame.Z * ptNewFrame.Z); ringHeight = springHeight / Coils / Rings; rotationAngle = Vector3d.CalculateAngle(ptOldFrame, ptNewFrame); rotationAxis = Vector3d.Cross(ptOldFrame, ptNewFrame); rotationMatrix = Matrix4d.CreateFromAxisAngle(rotationAxis, rotationAngle); GL.Color3(1.00f, 0.0f, 0.0f); GL.PushMatrix(); GL.Translate(ptStart); GL.MultMatrix(ref rotationMatrix); GL.Begin(BeginMode.LineStrip); z = 0; for (int i = 0; i < Coils; i++) { ringAngle = 0; for (int j = 0; j < Rings; j++) { GL.Vertex3(Math.Cos(ringAngle) * Radius, Math.Sin(ringAngle) * Radius, z); ringAngle += ringDelta; z += ringHeight; } } GL.End(); GL.PopMatrix(); }
3. 有粗细的弹簧
代码:
/// <summary> /// 绘制重弹簧 /// </summary> /// <param name="ptStart">弹簧的起点</param> /// <param name="ptEnd">弹簧的终点</param> /// <param name="Radius">弹簧的半径</param> /// <param name="Coils">弹簧的圈数</param> /// <param name="Rings">弹簧每圈的段数</param> /// <param name="Sides">弹簧每段的侧面数</param> /// <param name="TubeRadius">弹簧的段半径</param> void DrawHeavySpring(Vector3d ptStart, Vector3d ptEnd, double Radius = 50, double Coils = 10, int Rings = 60, int Sides = 18, double TubeRadius = 2) { double sideDelta = 2.0 * Math.PI / Sides; double ringDelta = 2.0 * Math.PI / Rings; double ringHeight; double theta = 0; double cosTheta = 1.0; double sinTheta = 0.0; double z; double phi, sinPhi, cosPhi; double dist; double springLength; Vector3d ptOldFrame, ptNewFrame; Vector3d rotationAxis; Matrix4d rotationMatrix; double rotationAngle; ptOldFrame = new Vector3d(0, 0, 1); ptNewFrame = ptEnd - ptStart; springLength = Math.Sqrt(ptNewFrame.X * ptNewFrame.X + ptNewFrame.Y * ptNewFrame.Y + ptNewFrame.Z * ptNewFrame.Z); ringHeight = springLength / Coils / Rings; rotationAngle = Vector3d.CalculateAngle(ptOldFrame, ptNewFrame); rotationAxis = Vector3d.Cross(ptOldFrame, ptNewFrame); rotationMatrix = Matrix4d.CreateFromAxisAngle(rotationAxis, rotationAngle); GL.Color3(1.00f, 0.0f, 0.0f); GL.PushMatrix(); GL.Translate(ptStart); GL.MultMatrix(ref rotationMatrix); z = 0; for (int i = 0; i < Coils; i++) { for (int j = 0; j < Rings; j++) { double theta1 = theta + ringDelta; double cosTheta1 = Math.Cos(theta1); double sinTheta1 = Math.Sin(theta1); GL.Begin(BeginMode.QuadStrip); phi = 0; for (int k = 0; k <= Sides; k++) { phi = phi + sideDelta; cosPhi = Math.Cos(phi); sinPhi = Math.Sin(phi); dist = Radius + (TubeRadius * cosPhi);
GL.Vertex3(cosTheta * dist, sinTheta * dist, z + TubeRadius * sinPhi); GL.Vertex3(cosTheta1 * dist, sinTheta1 * dist, z +ringHeight+ TubeRadius * sinPhi); } GL.End(); theta = theta1; cosTheta = cosTheta1; sinTheta = sinTheta1; z += ringHeight; } } GL.PopMatrix(); }