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