Formeln

Saturday, April 27, 2013

Procedural Meshes: The Torus

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 a torus. The torus has two radii: a radius from the center of the torus to the middle of the ring section and the ring radius:





Further parameters are number of sides and number of rings. These parameters are needed for the creation of the vertex and index buffers in order to determine how many vertices and indices are needed. The number of rings tells, how many sections the torus has. The number of sides is the number of sections for each ring.

The Formula

x = Cos(theta) * (radius + ringRadius * Cos(phi))
y = Sin(theta) * (radius + ringRadius * Cos(phi))
z = ringRadius * Sin(phi)


Both, theta and phi run from 0 to 2 * PI.

Creating the Vertex Buffer

I create the vertex buffer with a double nested for loop. The outer loop iterates over the number of sections and the variable theta is used to increment the angle of the current circle of vertices that is created. The inner for loop iterates over the number of vertices the torus will have per section. Here, the variable phi is incremented in the inner loop, to calculate the position of the current vertex.

int numVerticesPerRow = sides + 1;
int numVerticesPerColumn = rings + 1;

numVertices = numVerticesPerRow * numVerticesPerColumn;

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

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

float theta = 0.0f;
float phi = 0.0f;

float verticalAngularStride = (float)(Math.PI * 2.0f) / (float)rings;
float horizontalAngularStride = ((float)Math.PI * 2.0f) / (float)sides;

for (int verticalIt = 0; verticalIt < numVerticesPerColumn; verticalIt++)
{
    theta = verticalAngularStride * verticalIt;

    for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
    {
        phi = horizontalAngularStride * horizontalIt;

        // position
        float x = (float)Math.Cos(theta) * (radius + ringRadius * (float)Math.Cos(phi));
        float y = (float)Math.Sin(theta) * (radius + ringRadius * (float)Math.Cos(phi));
        float z = ringRadius * (float)Math.Sin(phi);

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

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

If you followed the tutorial on procedural meshes so far, the method of creating the index buffer will not as a surprise. I iterate again in a double nested for loop: the outer loop is for iterating the ring sections and the inner loop for iterating the rings themselves. The iterators are one less than the iterators in the double nested for loop above, because at each position of the innerloop, I create a patch of 2 triangles, looking one vertex index ahead.


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

for (int verticalIt = 0; verticalIt < rings; verticalIt++)
{
    for (int horizontalIt = 0; horizontalIt < sides; 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);
    }
}

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

Complete sources code of the class Torus:

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 Torus : 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;

    public Torus(float radius, float ringRadius, int sides, int rings)
    {
      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 = sides + 1;
      int numVerticesPerColumn = rings + 1;

      numVertices = numVerticesPerRow * numVerticesPerColumn;

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

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

      float theta = 0.0f;
      float phi = 0.0f;

      float verticalAngularStride = (float)(Math.PI * 2.0f) / (float)rings;
      float horizontalAngularStride = ((float)Math.PI * 2.0f) / (float)sides;

      for (int verticalIt = 0; verticalIt < numVerticesPerColumn; verticalIt++)
      {
        theta = verticalAngularStride * verticalIt;

        for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
        {
          phi = horizontalAngularStride * horizontalIt;

          // position
          float x = (float)Math.Cos(theta) * (radius + ringRadius * (float)Math.Cos(phi));
          float y = (float)Math.Sin(theta) * (radius + ringRadius * (float)Math.Cos(phi));
          float z = ringRadius * (float)Math.Sin(phi);

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

      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 = sides * rings * 6;
      indices = new DataStream(2 * numIndices, true, true);

      for (int verticalIt = 0; verticalIt < rings; verticalIt++)
      {
        for (int horizontalIt = 0; horizontalIt < sides; 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);
        }
      }

      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

This torus was created with the parameters radius = 1, ring radius = 0.25, number of sides = 36 and number of rings = 36.






This torus was created with the parameters radius = 1, ring radius = 0.5, number of sides = 8 and number of rings = 16.






You can download the code to this tutorial here.

No comments:

Post a Comment