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