// This code was written for the OpenTK library and has been released // to the Public Domain. // It is provided "as is" without express or implied warranty of any kind. using System; using System.Drawing; using System.Drawing.Imaging; using System.Diagnostics; using OpenTK; using OpenTK.Input; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; using Examples.Shapes; namespace Examples.Tutorial { [Example("Stencil CSG", ExampleCategory.OpenGL, "1.x", Documentation = "StencilCSG")] partial class StencilCSG : GameWindow { #region Model Related DrawableShape OperandB; DrawableShape OperandA; float MySphereZOffset = 0f; float MySphereXOffset = 0f; int Texture; #endregion Model Related string WindowTitle; bool ShowDebugWireFrame = true; float CameraZoom; float CameraRotX; float CameraRotY; Vector3 EyePosition = new Vector3(0f, 0f, 15f); #region Window public StencilCSG() : base(800, 600, new GraphicsMode(new ColorFormat(8, 8, 8, 8), 24, 8)) // request 8-bit stencil buffer { base.VSync = VSyncMode.Off; Keyboard.KeyDown += delegate(object sender, KeyboardKeyEventArgs e) { switch (e.Key) { case Key.Escape: this.Exit(); break; case Key.Space: ShowDebugWireFrame = !ShowDebugWireFrame; break; } }; } protected override void OnResize(EventArgs e) { GL.Viewport(0, 0, Width, Height); GL.MatrixMode(MatrixMode.Projection); Matrix4 p = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Width / (float)Height, 0.1f, 64.0f); GL.LoadMatrix(ref p); } #endregion Window protected override void OnLoad(EventArgs e) { #region Abort on platforms which will not be able to execute the ops properly /* if (!GL.SupportsExtension("VERSION_1_2")) { Trace.WriteLine("Aborting. OpenGL 1.2 or later required."); this.Exit(); } int[] t = new int[2]; GL.GetInteger(GetPName.MajorVersion, out t[0]); GL.GetInteger(GetPName.MinorVersion, out t[1]); Trace.WriteLine("OpenGL Context Version: " + t[0] + "." + t[1]); GL.GetInteger(GetPName.DepthBits, out t[0]); Trace.WriteLine("Depth Bits: " + t[0]); GL.GetInteger(GetPName.StencilBits, out t[1]); Trace.WriteLine("Stencil Bits: " + t[1]); if (t[0] < 16) { Trace.WriteLine("Aborting. Need at least 16 depth bits, only " + t[0] + " available."); this.Exit(); } if (t[1] < 1) { Trace.WriteLine("Aborting. Need at least 1 stencil bit, only " + t[1] + " available."); this.Exit(); } */ #endregion Abort on platforms which will not be able to execute the ops properly WindowTitle = "Cube-Sphere Stencil CSG " + GL.GetString(StringName.Renderer) + " (GL " + GL.GetString(StringName.Version) + ")"; #region GL States GL.ClearColor(.08f, .12f, .16f, 1f); GL.Enable(EnableCap.DepthTest); GL.DepthFunc(DepthFunction.Less); GL.ClearDepth(1.0); GL.Enable(EnableCap.StencilTest); GL.ClearStencil(0); GL.StencilMask(0xFFFFFFFF); // read&write GL.Enable(EnableCap.CullFace); GL.FrontFace(FrontFaceDirection.Ccw); GL.CullFace(CullFaceMode.Back); GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); GL.Color4(1f, 1f, 1f, 1f); GL.Enable(EnableCap.Lighting); GL.Enable(EnableCap.Light0); GL.ShadeModel(ShadingModel.Smooth); #endregion GL States #region Load Texture Bitmap bitmap = new Bitmap("Data/Textures/logo-dark.jpg"); bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY); GL.GenTextures(1, out Texture); GL.BindTexture(TextureTarget.Texture2D, Texture); BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); GL.Finish(); bitmap.UnlockBits(data); #endregion Load Texture OperandA = new ChamferCube(1.5, 2.0, 2.5, ChamferCube.SubDivs.Four, 0.42, true); OperandB = new SlicedSphere(2.0f, Vector3d.Zero, SlicedSphere.eSubdivisions.Three, new SlicedSphere.eDir[] { SlicedSphere.eDir.All }, true); #region Invert Operand B's Normals // only the inside of the operand is ever drawn to color buffers and lighting requires this. PrimitiveType tempPrimMode; VertexT2dN3dV3d[] tempVertices; uint[] tempIndices; OperandB.GetArraysforVBO(out tempPrimMode, out tempVertices, out tempIndices); OperandB.Dispose(); for (int i = 0; i < tempVertices.Length; i++) { tempVertices[i].Normal *= -1.0; tempVertices[i].Normal.Normalize(); } OperandB = new VboShape(ref tempPrimMode, ref tempVertices, ref tempIndices, true); #endregion Invert Operand B's Normals } protected override void OnUnload(EventArgs e) { GL.DeleteTextures(1, ref Texture); OperandA.Dispose(); OperandB.Dispose(); base.OnUnload(e); } protected override void OnUpdateFrame(FrameEventArgs e) { #region Magic numbers for camera CameraRotX = -Mouse.X * .5f; CameraRotY = Mouse.Y * .5f; CameraZoom = Mouse.Wheel * .2f; #endregion Magic numbers for camera } public void DrawOperandB() { GL.PushMatrix(); GL.Translate(Math.Cos(MySphereXOffset), -1f, Math.Cos(MySphereZOffset)); OperandB.Draw(); GL.PopMatrix(); } public void DrawOperandA() { GL.Enable(EnableCap.Texture2D); OperandA.Draw(); GL.Disable(EnableCap.Texture2D); } public void RenderCsg() { // first pass GL.Disable(EnableCap.StencilTest); GL.ColorMask(false, false, false, false); GL.CullFace(CullFaceMode.Front); DrawOperandB();// draw front-faces into depth buffer // use stencil plane to find parts of b in a GL.DepthMask(false); GL.Enable(EnableCap.StencilTest); GL.StencilFunc(StencilFunction.Always, 0, 0); GL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Incr); GL.CullFace(CullFaceMode.Back); DrawOperandA(); // increment the stencil where the front face of a is drawn GL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Decr); GL.CullFace(CullFaceMode.Front); DrawOperandA(); // decrement the stencil buffer where the back face of a is drawn GL.DepthMask(true); GL.Disable(EnableCap.DepthTest); GL.ColorMask(true, true, true, true); GL.StencilFunc(StencilFunction.Notequal, 0, 1); DrawOperandB(); // draw the part of b that's in a // fix depth GL.ColorMask(false, false, false, false); GL.Enable(EnableCap.DepthTest); GL.Disable(EnableCap.StencilTest); GL.DepthFunc(DepthFunction.Always); DrawOperandA(); GL.DepthFunc(DepthFunction.Less); // second pass GL.CullFace(CullFaceMode.Back); DrawOperandA(); GL.DepthMask(false); GL.Enable(EnableCap.StencilTest); GL.StencilFunc(StencilFunction.Always, 0, 0); GL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Incr); DrawOperandB(); // increment the stencil where the front face of b is drawn GL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Decr); GL.CullFace(CullFaceMode.Front); DrawOperandB(); // decrement the stencil buffer where the back face of b is drawn GL.DepthMask(true); GL.Disable(EnableCap.DepthTest); GL.ColorMask(true, true, true, true); GL.StencilFunc(StencilFunction.Equal, 0, 1); GL.CullFace(CullFaceMode.Back); DrawOperandA(); // draw the part of a that's in b GL.Enable(EnableCap.DepthTest); } protected override void OnRenderFrame(FrameEventArgs e) { this.Title = WindowTitle + " FPS: " + (1f / e.Time).ToString("0."); MySphereZOffset += (float)(e.Time * 3.1); MySphereXOffset += (float)(e.Time * 4.2); #region Transform setup GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit); // Camera GL.MatrixMode(MatrixMode.Modelview); Matrix4 mv = Matrix4.LookAt(EyePosition, Vector3.Zero, Vector3.UnitY); GL.LoadMatrix(ref mv); GL.Translate(0f, 0f, CameraZoom); GL.Rotate(CameraRotX, Vector3.UnitY); GL.Rotate(CameraRotY, Vector3.UnitX); #endregion Transform setup RenderCsg(); // --------------------------------- if (ShowDebugWireFrame) { GL.Color3(System.Drawing.Color.LightGray); GL.Disable(EnableCap.StencilTest); GL.Disable(EnableCap.Lighting); //GL.Disable( EnableCap.DepthTest ); GL.PolygonMode(MaterialFace.Front, PolygonMode.Line); DrawOperandB(); GL.PolygonMode(MaterialFace.Front, PolygonMode.Fill); GL.Enable(EnableCap.DepthTest); GL.Enable(EnableCap.Lighting); GL.Enable(EnableCap.StencilTest); } this.SwapBuffers(); } [STAThread] static void Main() { using (StencilCSG example = new StencilCSG()) { Utilities.SetWindowTitle(example); example.Run(30.0, 0.0); } } } }