Formeln

Saturday, April 27, 2013

Procedural Meshes: The Superellipsoid

Introduction


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

The superellipsoid is basically a sphere with additional exponents n1 and n2  for manipulating the trigonometric functions. You can create cubes with round edges, a cube, a sphere, a cylinder and many other geometric objects with it, when using the according values for n1 and n2.



The Formula

I took the formula for the superellipsoid from:
http://paulbourke.net/geometry/superellipse/

x = radius * Cos^n1(theta) * Cos^n2(phi)
y = radius * Cos^n1(theta) * Sin^n2(phi)
z = radius * Sin^n1(theta)

Basically, this is the same formula as for the sphere, with two additional exponents n1 and n2. Both, n1 and n2 have to be bigger than 0 to produce valid meshes. Although values over 10 don't add any significant change to the appearence of the mesh. Play around with the parameters and see for yourself ;)


Creating the Vertex Buffer

Theta runs in the outer loop from PI/2 to -PI/2 and phi runs from zero to 2*PI.
I think the only obscure thing in the code is use of the Math.Pow function.
At first, I check if cosTheta, sinTheta, cosPhi and sinPhi are zero. If they are zero, 0 is asigned to the according powX variables. Then I have to check, if for example cosTheta is a negative value.
Small negative values can produce a NaN (Not A Number) value from the Math.Pow function.

Instead of 
  Math.Pow( - littleNumber, anyOtherNumber ), 
I am calculating:
- Math.Pow(   littleNumber, anyOtherNumber )




int numHorizontalVertices = numVerticesPerLayer + 1;
int numVerticalVertices = numVerticesPerLayer + 1;

numVertices = numHorizontalVertices * numVerticalVertices;
vertexSizeInBytes = 12;
int vertexBufferSizeInBytes = vertexSizeInBytes * numVertices;

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

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

float verticalStep = ((float)Math.PI) / (float)numVerticesPerLayer;
float horizontalStep = ((float)Math.PI * 2) / (float)numVerticesPerLayer;

for (int verticalIt = 0; verticalIt < numVerticalVertices; verticalIt++)
{

  theta = ((float)Math.PI / 2) - verticalStep * verticalIt;

  for (int horizontalIt = 0; horizontalIt < numHorizontalVertices; horizontalIt++)
  {

    phi = horizontalStep * horizontalIt;

    double cosTheta = Math.Cos(theta);
    double sinTheta = Math.Sin(theta);
    double cosPhi = Math.Cos(phi);
    double sinPhi = Math.Sin(phi);

    double powCosTheta = cosTheta == 0.0f ? 0 : Math.Sign(cosTheta) == -1 ? -Math.Pow(-cosTheta, n1) : Math.Pow(cosTheta, n1);
    double powSinTheta = sinTheta == 0.0f ? 0 : Math.Sign(sinTheta) == -1 ? -Math.Pow(-sinTheta, n1) : Math.Pow(sinTheta, n1);
    double powCosPhi  = cosPhi == 0.0f ? 0 : Math.Sign(cosPhi) == -1 ? -Math.Pow(-cosPhi, n2) : Math.Pow(cosPhi, n2);
    double powSinPhi = sinPhi == 0.0f ? 0 : Math.Sign(sinPhi) == -1 ? -Math.Pow(-sinPhi, n2) : Math.Pow(sinPhi, n2);
 

    double x = radius * powCosTheta * powCosPhi;
    double y = radius * powCosTheta * powSinPhi;
    double z = radius * powSinTheta;

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

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,
  vertexBufferSizeInBytes,
  ResourceUsage.Default,
  BindFlags.VertexBuffer,
  CpuAccessFlags.None,
  ResourceOptionFlags.None, 0);



Creating the Index Buffer

Nothing new here, please refer to the grid mesh tutorial for details.

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

try
{
  for (int verticalIt = 0; verticalIt < numVerticesPerLayer; verticalIt++)
  {

    for (int horizontalIt = 0; horizontalIt < numVerticesPerLayer; horizontalIt++)
    {
      short lu = (short)(horizontalIt + verticalIt * (numHorizontalVertices));
      short ru = (short)((horizontalIt + 1) + verticalIt * (numHorizontalVertices));

      short ld = (short)(horizontalIt + (verticalIt + 1) * (numHorizontalVertices));
      short rd = (short)((horizontalIt + 1) + (verticalIt + 1) * (numHorizontalVertices));

      indices.Write(lu);
      indices.Write(rd);
      indices.Write(ld);
      

      indices.Write(rd);
      indices.Write(lu);
      indices.Write(ru);
    }
  }
}
catch (Exception ex)
{
}

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;

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

    InputLayout layout;

    int numVertices = 0;
    int numIndices = 0;

    ShaderSignature inputSignature;
    EffectTechnique technique;
    EffectPass pass;

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

    double n1 = 0;
    double n2 = 0;

    float radius = 0.0f;

    public SuperEllipsoid(int numVerticesPerLayer, float radius, float n1, float n2)
    {
      try
      {
        this.n1 = n1;
        this.n2 = n2;
        this.radius = radius;
        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 numHorizontalVertices = numVerticesPerLayer + 1;
      int numVerticalVertices = numVerticesPerLayer + 1;

      numVertices = numHorizontalVertices * numVerticalVertices;
      vertexSizeInBytes = 12;
      int vertexBufferSizeInBytes = vertexSizeInBytes * numVertices;

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

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

      float verticalStep = ((float)Math.PI) / (float)numVerticesPerLayer;
      float horizontalStep = ((float)Math.PI * 2) / (float)numVerticesPerLayer;

      for (int verticalIt = 0; verticalIt < numVerticalVertices; verticalIt++)
      {

        theta = -((float)Math.PI / 2) + verticalStep * verticalIt;

        for (int horizontalIt = 0; horizontalIt < numHorizontalVertices; horizontalIt++)
        {

          phi = horizontalStep * horizontalIt;

          double cosTheta = Math.Cos(theta);
          double sinTheta = Math.Sin(theta);
          double cosPhi = Math.Cos(phi);
          double sinPhi = Math.Sin(phi);

          double powCosTheta = cosTheta == 0.0f ? 0 : Math.Sign(cosTheta) == -1 ? -Math.Pow(-cosTheta, n1) : Math.Pow(cosTheta, n1);
          double powSinTheta = sinTheta == 0.0f ? 0 : Math.Sign(sinTheta) == -1 ? -Math.Pow(-sinTheta, n1) : Math.Pow(sinTheta, n1);
          double powCosPhi  = cosPhi == 0.0f ? 0 : Math.Sign(cosPhi) == -1 ? -Math.Pow(-cosPhi, n2) : Math.Pow(cosPhi, n2);
          double powSinPhi = sinPhi == 0.0f ? 0 : Math.Sign(sinPhi) == -1 ? -Math.Pow(-sinPhi, n2) : Math.Pow(sinPhi, n2);
       

          double x = radius * powCosTheta * powCosPhi;
          double y = radius * powCosTheta * powSinPhi;
          double z = radius * powSinTheta;

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

      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,
        vertexBufferSizeInBytes,
        ResourceUsage.Default,
        BindFlags.VertexBuffer,
        CpuAccessFlags.None,
        ResourceOptionFlags.None, 0);

      // creating the index buffer
      numIndices = 6 * numVertices;
      indices = new DataStream(2 * numIndices, true, true);
   
      try
      {
        for (int verticalIt = 0; verticalIt < numVerticesPerLayer; verticalIt++)
        {

          for (int horizontalIt = 0; horizontalIt < numVerticesPerLayer; horizontalIt++)
          {
            short lu = (short)(horizontalIt + verticalIt * (numHorizontalVertices));
            short ru = (short)((horizontalIt + 1) + verticalIt * (numHorizontalVertices));

            short ld = (short)(horizontalIt + (verticalIt + 1) * (numHorizontalVertices));
            short rd = (short)((horizontalIt + 1) + (verticalIt + 1) * (numHorizontalVertices));

            indices.Write(lu);
            indices.Write(rd);
            indices.Write(ld);
     
            indices.Write(rd);
            indices.Write(lu);
            indices.Write(ru);
          }
        }
      }
      catch (Exception ex)
      {
      }

      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, vertexSizeInBytes, 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()
    {
      indexBuffer.Dispose();
      vertexBuffer.Dispose();
      effect.Dispose();
    }
  }
}



Result


All meshes shown below were created with radius = 1.0 and 32 * 32 vertices mesh.
Only the values for n1 and n2 differ.


n1 = 0 and n2 = 0:


n1 = 0 and n2 = 1:


n1 = 1 and n2 = 0:


n1 = 1 and n2 = 1:


n1 = 0.4 and n2 = 0.4:


n1 = 2 and n2 = 2:


n1 = 4 and n2 = 4:



I added a second class SuperEllipsoidCol which has the same colors as the color cube:

With 32 x 32 vertices, radius 0.5, n1 = 0.2 and n2 = 0.2:




You can download the source code for this tutorial here. You can play around with the parameters in the RenderControl.cs class.

Have fun!

No comments:

Post a Comment