Formeln

Friday, April 26, 2013

Procdural Meshes: The Cylinder

Introduction


This tutorial is one part of a series of tutorials about generating procedural meshes. See here for an outline.

In this tutorial I will show you how to create procedurally a cylinder mesh. The cylinders bottom is in the x-z plane and the top at a given height. You can also parametrize the radius of the cylinders top and bottom.


The idea behind generating the cylinder is quite simple: create two circles of vertices and afterwards create the indices for the triangles between the two circles. This leaves us with an open top and bottom of the cylinder. So we need to append two vertices to the vertex buffer: one vertex at the bottom of the cylinder and one vertex at the top of the cylinder. Finally, we can create the indices for the top and bottom  of the cylinder.

The Formula

The formula for creating the circle is as follows:

x = radius * Cos(theta)
y = radius * Sin(theta)
z = height

Theta runs from 0 to 2 * PI.

Creating the Vertex Buffer

At first I create the vertices for the upper circle and then the vertices at the bottom of the cylinder are created. After that, I append the two vertices at the top and the bottom to the vertex buffer, which are needed to close the mesh at the top and bottom.

int numVerticesPerRow = slices + 1;

numVertices = numVerticesPerRow * 2 + 2;

vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
int SizeOfVertexBufferInBytes = numVertices * vertexStride;

vertices = new DataStream(SizeOfVertexBufferInBytes, true, true);

float theta = 0.0f;
float horizontalAngularStride = ((float)Math.PI * 2) / (float)slices;

for (int verticalIt = 0; verticalIt < 2; verticalIt++)
{
  for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
  {
    float x;
    float y;
    float z;

    theta = (horizontalAngularStride * horizontalIt);

    if (verticalIt == 0)
    {
      // upper circle
      x = radiusTop * (float)Math.Cos(theta);
      y = radiusTop * (float)Math.Sin(theta);
      z = height;
    }
    else
    {
      // lower circle
      x = radiusBottom * (float)Math.Cos(theta);
      y = radiusBottom * (float)Math.Sin(theta);
      z = 0;
    }

    Vector3 position = new Vector3(x, z, y);
    vertices.Write(position);
  }
}

vertices.Write(new Vector3(0, height, 0));
vertices.Write(new Vector3(0, 0, 0));


vertices.Position = 0;

// create the vertex layout and buffer
var elements = new[] { 
          new InputElement("POSITION", 0, Format.R32G32B32_Float, 0)
      };
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);


vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,
    vertices,
    SizeOfVertexBufferInBytes,
    ResourceUsage.Default,
    BindFlags.VertexBuffer,
    CpuAccessFlags.None,
    ResourceOptionFlags.None,
    0);


Creating the Index Buffer

At first the two circles are filled with triangles, by patching the mesh with triangles between the upper circle of vertices and the lower circle of vertices. After that, the triangles at the top and the bottom are created in the index buffer with the help of the two vertices at the middle of the top and the bottom of the cylinder.


numIndices = slices * 2 * 6;
indices = new DataStream(2 * numIndices, true, true);

for (int verticalIt = 0; verticalIt < 1; verticalIt++)
{
  for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
  {
    short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
    short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));

    short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
    short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));

    indices.Write(lt);
    indices.Write(rt);
    indices.Write(lb);

    indices.Write(rt);
    indices.Write(rb);
    indices.Write(lb);
  }
}

for (int verticalIt = 0; verticalIt < 1; verticalIt++)
{
  for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
  {
    short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
    short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));

    short patchIndexTop = (short)(numVerticesPerRow * 2);

    indices.Write(lt);
    indices.Write(patchIndexTop);
    indices.Write(rt);
  }
}

for (int verticalIt = 0; verticalIt < 1; verticalIt++)
{
  for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
  {
    short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
    short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));


    short patchIndexBottom = (short)(numVerticesPerRow * 2 + 1);
    indices.Write(lb);
    indices.Write(rb);
    indices.Write(patchIndexBottom);
  }
}

indices.Position = 0;

indexBuffer = new SlimDX.Direct3D11.Buffer(
    DeviceManager.Instance.device,
    indices,
    2 * numIndices,
    ResourceUsage.Default,
    BindFlags.IndexBuffer,
    CpuAccessFlags.None,
    ResourceOptionFlags.None,
    0);


Source Code



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using System.Runtime.InteropServices;
using System.Drawing;

namespace Apparat.Renderables
{
  public class Cylinder : Renderable
  {
    SlimDX.Direct3D11.Buffer vertexBuffer;
    SlimDX.Direct3D11.Buffer indexBuffer;
    DataStream vertices;
    DataStream indices;

    InputLayout layout;

    int numVertices = 0;
    int numIndices = 0;

    int vertexStride = 0;

    ShaderSignature inputSignature;
    EffectTechnique technique;
    EffectPass pass;

    Effect effect;
    EffectMatrixVariable tmat;
    EffectVectorVariable mCol;
    EffectVectorVariable wfCol;

    float radius = 0;

    public Cylinder(float height, float radiusBottom, float radiusTop, int slices)
    {
      try
      {
        using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
            "Shaders/transformEffectWireframe.fx",
            "Render",
            "fx_5_0",
            ShaderFlags.EnableStrictness,
            EffectFlags.None))
        {
          effect = new Effect(DeviceManager.Instance.device, effectByteCode);
          technique = effect.GetTechniqueByIndex(0);
          pass = technique.GetPassByIndex(0);
          inputSignature = pass.Description.Signature;
        }
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.ToString());
      }

      tmat = effect.GetVariableByName("gWVP").AsMatrix();
      mCol = effect.GetVariableByName("colorSolid").AsVector();
      wfCol = effect.GetVariableByName("colorWireframe").AsVector();

      mCol.Set(new Color4(1, 0, 1, 0));
      wfCol.Set(new Color4(1, 0, 0, 0));

      int numVerticesPerRow = slices + 1;

      numVertices = numVerticesPerRow * 2 + 2;

      vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
      int SizeOfVertexBufferInBytes = numVertices * vertexStride;

      vertices = new DataStream(SizeOfVertexBufferInBytes, true, true);

      float theta = 0.0f;
      float horizontalAngularStride = ((float)Math.PI * 2) / (float)slices;

      for (int verticalIt = 0; verticalIt < 2; verticalIt++)
      {
        for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
        {
          float x;
          float y;
          float z;

          theta = (horizontalAngularStride * horizontalIt);

          if (verticalIt == 0)
          {
            // upper circle
            x = radiusTop * (float)Math.Cos(theta);
            y = radiusTop * (float)Math.Sin(theta);
            z = height;
          }
          else
          {
            // lower circle
            x = radiusBottom * (float)Math.Cos(theta);
            y = radiusBottom * (float)Math.Sin(theta);
            z = 0;
          }

          Vector3 position = new Vector3(x, z, y);
          vertices.Write(position);
        }
      }
      
      vertices.Write(new Vector3(0, height, 0));
      vertices.Write(new Vector3(0, 0, 0));


      vertices.Position = 0;

      // create the vertex layout and buffer
      var elements = new[] { 
                new InputElement("POSITION", 0, Format.R32G32B32_Float, 0)
            };
      layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);


      vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,
          vertices,
          SizeOfVertexBufferInBytes,
          ResourceUsage.Default,
          BindFlags.VertexBuffer,
          CpuAccessFlags.None,
          ResourceOptionFlags.None,
          0);

      numIndices = slices * 2 * 6;
      indices = new DataStream(2 * numIndices, true, true);

      for (int verticalIt = 0; verticalIt < 1; verticalIt++)
      {
        for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
        {
          short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
          short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));

          short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
          short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));

          indices.Write(lt);
          indices.Write(rt);
          indices.Write(lb);

          indices.Write(rt);
          indices.Write(rb);
          indices.Write(lb);
        }
      }

      for (int verticalIt = 0; verticalIt < 1; verticalIt++)
      {
        for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
        {
          short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
          short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));

          short patchIndexTop = (short)(numVerticesPerRow * 2);

          indices.Write(lt);
          indices.Write(patchIndexTop);
          indices.Write(rt);
        }
      }

      for (int verticalIt = 0; verticalIt < 1; verticalIt++)
      {
        for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
        {
          short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
          short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));


          short patchIndexBottom = (short)(numVerticesPerRow * 2 + 1);
          indices.Write(lb);
          indices.Write(rb);
          indices.Write(patchIndexBottom);
        }
      }

      indices.Position = 0;

      indexBuffer = new SlimDX.Direct3D11.Buffer(
          DeviceManager.Instance.device,
          indices,
          2 * numIndices,
          ResourceUsage.Default,
          BindFlags.IndexBuffer,
          CpuAccessFlags.None,
          ResourceOptionFlags.None,
          0);
    }

    public override void render()
    {
      Matrix ViewPerspective = CameraManager.Instance.ViewPerspective;

      tmat.SetMatrix(ViewPerspective);

      // configure the Input Assembler portion of the pipeline with the vertex data
      DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
      DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
      DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, vertexStride, 0));
      DeviceManager.Instance.context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R16_UInt, 0);

      technique = effect.GetTechniqueByName("Render");

      EffectTechniqueDescription techDesc;
      techDesc = technique.Description;

      for (int p = 0; p < techDesc.PassCount; ++p)
      {
        technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
        DeviceManager.Instance.context.DrawIndexed(numIndices, 0, 0);
      }
    }

    public override void dispose()
    {

    }
  }
}


Result


For this cylinder, I used the following parameters: height = 2, radius at the bottom = 1, radius at the top = 1 and slices = 16.





If you want to create a tip, you can set the radius at to top to zero:




You can download the source code for this tutorial here.

No comments:

Post a Comment