Combine 3D objects in XNA 4
- by Christoph
Currently I am writing on my thesis for university, the theme I am working on is 3D Visualization of hierarchical structures using cone trees.
I want to do is to draw a cone and arrange a number of spheres at the bottom of the cone. The spheres should be arranged according to the radius and the number of spheres correctly.
As you can imagine I need a lot of these cone/sphere combinations.
First Attempt
I was able to find some tutorials that helped with drawing cones and spheres.
Cone
public Cone(GraphicsDevice device, float height, int tessellation, string name, List<Sphere> children)
{
//prepare children and calculate the children spacing and radius of the cone
if (children == null || children.Count == 0)
{
throw new ArgumentNullException("children");
}
this.Height = height;
this.Name = name;
this.Children = children;
//create the cone
if (tessellation < 3)
{
throw new ArgumentOutOfRangeException("tessellation");
}
//Create a ring of triangels around the outside of the cones bottom
for (int i = 0; i < tessellation; i++)
{
Vector3 normal = this.GetCircleVector(i, tessellation);
// add the vertices for the top of the cone
base.AddVertex(Vector3.Up * height, normal);
//add the bottom circle
base.AddVertex(normal * this.radius + Vector3.Down * height, normal);
//Add indices
base.AddIndex(i * 2);
base.AddIndex(i * 2 + 1);
base.AddIndex((i * 2 + 2) % (tessellation * 2));
base.AddIndex(i * 2 + 1);
base.AddIndex((i * 2 + 3) % (tessellation * 2));
base.AddIndex((i * 2 + 2) % (tessellation * 2));
}
//create flate triangle to seal the bottom
this.CreateCap(tessellation, height, this.Radius, Vector3.Down);
base.InitializePrimitive(device);
}
Sphere
public void Initialize(GraphicsDevice device, Vector3 qi)
{
int verticalSegments = this.Tesselation;
int horizontalSegments = this.Tesselation * 2;
//single vertex on the bottom
base.AddVertex((qi * this.Radius) + this.lowering, Vector3.Down);
for (int i = 0; i < verticalSegments; i++)
{
float latitude = ((i + 1) * MathHelper.Pi / verticalSegments) - MathHelper.PiOver2;
float dy = (float)Math.Sin(latitude);
float dxz = (float)Math.Cos(latitude);
//Create a singe ring of latitudes
for (int j = 0; j < horizontalSegments; j++)
{
float longitude = j * MathHelper.TwoPi / horizontalSegments;
float dx = (float)Math.Cos(longitude) * dxz;
float dz = (float)Math.Sin(longitude) * dxz;
Vector3 normal = new Vector3(dx, dy, dz);
base.AddVertex(normal * this.Radius, normal);
}
}
// Finish with a single vertex at the top of the sphere.
AddVertex((qi * this.Radius) + this.lowering, Vector3.Up);
// Create a fan connecting the bottom vertex to the bottom latitude ring.
for (int i = 0; i < horizontalSegments; i++)
{
AddIndex(0);
AddIndex(1 + (i + 1) % horizontalSegments);
AddIndex(1 + i);
}
// Fill the sphere body with triangles joining each pair of latitude rings.
for (int i = 0; i < verticalSegments - 2; i++)
{
for (int j = 0; j < horizontalSegments; j++)
{
int nextI = i + 1;
int nextJ = (j + 1) % horizontalSegments;
base.AddIndex(1 + i * horizontalSegments + j);
base.AddIndex(1 + i * horizontalSegments + nextJ);
base.AddIndex(1 + nextI * horizontalSegments + j);
base.AddIndex(1 + i * horizontalSegments + nextJ);
base.AddIndex(1 + nextI * horizontalSegments + nextJ);
base.AddIndex(1 + nextI * horizontalSegments + j);
}
}
// Create a fan connecting the top vertex to the top latitude ring.
for (int i = 0; i < horizontalSegments; i++)
{
base.AddIndex(CurrentVertex - 1);
base.AddIndex(CurrentVertex - 2 - (i + 1) % horizontalSegments);
base.AddIndex(CurrentVertex - 2 - i);
}
base.InitializePrimitive(device);
}
The tricky part now is to arrange the spheres at the bottom of the cone. I tried is to draw just the cone and then draw the spheres. I need a lot of these cones, so it would be pretty hard to calculate all the positions correctly.
Second Attempt
So the second try was to generate a object that builds all vertices of the cone and all of the spheres at once. So I was hoping to render a cone with all its spheres arranged correctly. After a short debug I found out that the cone is created and the first sphere, when it turn of the second sphere I am running into an OutOfBoundsException of ushort.MaxValue.
Cone and Spheres
public ConeWithSpheres(GraphicsDevice device, float height, float coneDiameter, float sphereDiameter,
int coneTessellation, int sphereTessellation, int numberOfSpheres)
{
if (coneTessellation < 3)
{
throw new ArgumentException(string.Format("{0} is to small for the tessellation of the cone. The number must be greater or equal to 3", coneTessellation));
}
if (sphereTessellation < 3)
{
throw new ArgumentException(string.Format("{0} is to small for the tessellation of the sphere. The number must be greater or equal to 3", sphereTessellation));
}
//set properties
this.Height = height;
this.ConeDiameter = coneDiameter;
this.SphereDiameter = sphereDiameter;
this.NumberOfChildren = numberOfSpheres;
//end set properties
//generate the cone
this.GenerateCone(device, coneTessellation);
//generate the spheres
//vector that defines the Y position of the sphere on the cones bottom
Vector3 lowering = new Vector3(0, 0.888f, 0);
this.GenerateSpheres(device, sphereTessellation, numberOfSpheres, lowering);
}
// ------ GENERATE CONE ------
private void GenerateCone(GraphicsDevice device, int coneTessellation)
{
int doubleTessellation = coneTessellation * 2;
//Create a ring of triangels around the outside of the cones bottom
for (int index = 0; index < coneTessellation; index++)
{
Vector3 normal = this.GetCircleVector(index, coneTessellation);
//add the vertices for the top of the cone
base.AddVertex(Vector3.Up * this.Height, normal);
//add the bottom of the cone
base.AddVertex(normal * this.ConeRadius + Vector3.Down * this.Height, normal);
//add indices
base.AddIndex(index * 2);
base.AddIndex(index * 2 + 1);
base.AddIndex((index * 2 + 2) % doubleTessellation);
base.AddIndex(index * 2 + 1);
base.AddIndex((index * 2 + 3) % doubleTessellation);
base.AddIndex((index * 2 + 2) % doubleTessellation);
}
//create flate triangle to seal the bottom
this.CreateCap(coneTessellation, this.Height, this.ConeRadius, Vector3.Down);
base.InitializePrimitive(device);
}
// ------ GENERATE SPHERES ------
private void GenerateSpheres(GraphicsDevice device, int sphereTessellation, int numberOfSpheres, Vector3 lowering)
{
int verticalSegments = sphereTessellation;
int horizontalSegments = sphereTessellation * 2;
for (int childCount = 1; childCount < numberOfSpheres; childCount++)
{
//single vertex at the bottom of the sphere
base.AddVertex((this.GetCircleVector(childCount, this.NumberOfChildren) * this.SphereRadius) + lowering,
Vector3.Down);
for (int verticalSegmentsCount = 0; verticalSegmentsCount < verticalSegments; verticalSegmentsCount++)
{
float latitude = ((verticalSegmentsCount + 1) * MathHelper.Pi / verticalSegments) - MathHelper.PiOver2;
float dy = (float)Math.Sin(latitude);
float dxz = (float)Math.Cos(latitude);
//create a single ring of latitudes
for (int horizontalSegmentsCount = 0; horizontalSegmentsCount < horizontalSegments; horizontalSegmentsCount++)
{
float longitude = horizontalSegmentsCount * MathHelper.TwoPi / horizontalSegments;
float dx = (float)Math.Cos(longitude) * dxz;
float dz = (float)Math.Sin(longitude) * dxz;
Vector3 normal = new Vector3(dx, dy, dz);
base.AddVertex((normal * this.SphereRadius) + lowering, normal);
}
}
//finish with a single vertex at the top of the sphere
base.AddVertex((this.GetCircleVector(childCount, this.NumberOfChildren) * this.SphereRadius) + lowering,
Vector3.Up);
//create a fan connecting the bottom vertex to the bottom latitude ring
for (int i = 0; i < horizontalSegments; i++)
{
base.AddIndex(0);
base.AddIndex(1 + (i + 1) % horizontalSegments);
base.AddIndex(1 + i);
}
//Fill the sphere body with triangles joining each pair of latitude rings
for (int i = 0; i < verticalSegments - 2; i++)
{
for (int j = 0; j < horizontalSegments; j++)
{
int nextI = i + 1;
int nextJ = (j + 1) % horizontalSegments;
base.AddIndex(1 + i * horizontalSegments + j);
base.AddIndex(1 + i * horizontalSegments + nextJ);
base.AddIndex(1 + nextI * horizontalSegments + j);
base.AddIndex(1 + i * horizontalSegments + nextJ);
base.AddIndex(1 + nextI * horizontalSegments + nextJ);
base.AddIndex(1 + nextI * horizontalSegments + j);
}
}
//create a fan connecting the top vertiex to the top latitude
for (int i = 0; i < horizontalSegments; i++)
{
base.AddIndex(this.CurrentVertex - 1);
base.AddIndex(this.CurrentVertex - 2 - (i + 1) % horizontalSegments);
base.AddIndex(this.CurrentVertex - 2 - i);
}
base.InitializePrimitive(device);
}
}
Any ideas how I could fix this?