Unity3D - How to add textures to a mesh - c#

I am making a cubic voxel game. I have chunks, world, blocks and mesh generation done, but there's one problem - I could not do the texturing.
Everything I need is just add a texture to a side of a 3D mesh (Texture of every is different!). I've seen some implementations but it's hard to read somebody else's code (I've tried to use them, but it didn't work). I've tried to do this by myself, but with no results.
Can anybody explain how to do this??
Here is my current code:
[ExecuteInEditMode]
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class Chunk : MonoBehaviour
{
private ushort[] _voxels = new ushort[16 * 16 * 16];
private MeshFilter meshFilter;
private Vector3[] cubeVertices = new[] {
new Vector3 (0, 0, 0),
new Vector3 (1, 0, 0),
new Vector3 (1, 1, 0),
new Vector3 (0, 1, 0),
new Vector3 (0, 1, 1),
new Vector3 (1, 1, 1),
new Vector3 (1, 0, 1),
new Vector3 (0, 0, 1),
};
private int[] cubeTriangles = new[] {
// Front
0, 2, 1,
0, 3, 2,
// Top
2, 3, 4,
2, 4, 5,
// Right
1, 2, 5,
1, 5, 6,
// Left
0, 7, 4,
0, 4, 3,
// Back
5, 4, 7,
5, 7, 6,
// Bottom
0, 6, 7,
0, 1, 6
};
public ushort this[int x, int y, int z]
{
get { return _voxels[x * 16 * 16 + y * 16 + z]; }
set { _voxels[x * 16 * 16 + y * 16 + z] = value; }
}
void Start()
{
meshFilter = GetComponent<MeshFilter>();
}
private void Update()
{
GenerateMesh();
}
public void GenerateMesh()
{
Mesh mesh = new Mesh();
List<Vector3> vertices = new List<Vector3>();
List<int> triangles = new List<int>();
for (var x = 0; x < 16; x++)
{
for (var y = 0; y < 16; y++)
{
for (var z = 0; z < 16; z++)
{
var voxelType = this[x, y, z];
if (voxelType == 0)
continue;
var pos = new Vector3(x, y, z);
var verticesPos = vertices.Count;
foreach (var vert in cubeVertices)
vertices.Add(pos + vert);
foreach (var tri in cubeTriangles)
triangles.Add(verticesPos + tri);
}
}
}
mesh.SetVertices(vertices);
mesh.SetTriangles(triangles.ToArray(), 0);
meshFilter.mesh = mesh;
}
}
NOTE: This is a repost with many edits so it is focused on one problem plus has better explanation. Sorry for that.

Like your SetVertices() and SetTriangles(), you can call a SetUVs() with a list of the UV coordinates of each vertex on your texture.
The UV list size must match the vertices list size!
The UV coordinate are expressed as Vector2 with values between 0 and 1.
For example, to apply the whole texture on the front face of your cube, you have the first 4 uvs like this:
private Vector2[] cubeUVs = new[] {
new Vector2 (0, 0),
new Vector2 (1, 0),
new Vector2 (1, 1),
new Vector2 (0, 1),
...
}
...
mesh.SetUVs(0, cubeUVs);
If your texture is not a square, then it will be stretched.
You should also call RecalculateBounds() and RecalculateNormals() at the end of your GenerateMesh() method to avoid some issues later.
EDIT
If you really want different texture files for each side of the cube, then the cleanest and most performant solution for me is to set a different VertexColor for each side of your cube, eg. (1,0,0), (0,1,0), (0,0,1), (1,1,0), (1,0,1) and (0,1,1).
However, you will have to duplicate all your vertices 3 times. (because the vertex color is bound to a vertex, and each vertex of a cube belongs to 3 sides)
(You still have to set the UVs like I said previously, but each side has the whole texture instead of only a part of the texture)
Then, you will have to create a custom shader with 6 textures in inputs (one for each side).
And in the fragment function, you select the right texture color according to the vertex color.
You can for that, do some if to select the texture, but it will be not very performant:
float3 finalColor;
if(vertexColor.r > 0.5f && vertexColor.g < 0.5f && vertexColor.b < 0.5f)
{
finalColor = text2D(_TopTexture, in.uv);
}
else if(...)
{
...
}
...
Or if you want more perf (with a lot of cubes), you can instead do some multiplications to select the right texture:
float3 topTexColor = text2D(_TopTexture, in.uv) * vertexColor.r * (1.0f - vertexColor.g) * (1.0f - vertexColor.b);
float3 frontTexColor = ...;
...
float3 finalColor = topTexColor + frontTexColor + ...;

Related

Meshes created from code don't have a position unity

So, I tried to create a grid so that I can instantiate objects on it. I check for the position of said hit object (one of the squares I created) and then set the instantiated object to that position. Problem is, the squares I created with code don't have a position and are all set to 0, 0, 0.
{
GameObject tileObject = new GameObject(string.Format("{0}, {1}", x, y));
tileObject.transform.parent = transform;
Mesh mesh = new Mesh();
tileObject.AddComponent<MeshFilter>().mesh = mesh;
tileObject.AddComponent<MeshRenderer>().material = tileMaterial;
Vector3[] vertices = new Vector3[4];
vertices[0] = new Vector3(x * tileSize, 0, y * tileSize);
vertices[1] = new Vector3(x * tileSize, 0, (y +1) * tileSize);
vertices[2] = new Vector3((x +1) * tileSize, 0, y * tileSize);
vertices[3] = new Vector3((x +1) * tileSize, 0, (y +1) * tileSize);
int[] tris = new int[] { 0, 1, 2, 1, 3, 2 };
mesh.vertices = vertices;
mesh.triangles = tris;
mesh.RecalculateNormals();
tileObject.layer = LayerMask.NameToLayer("Tile");
tileObject.AddComponent<BoxCollider>();
//var xPos = Mathf.Round(x);
//var yPos = Mathf.Round(y);
//tileObject.gameObject.transform.position = new Vector3(xPos , 0f, yPos);
return tileObject;
}```
As said your issue is that you leave all tiles on the position 0,0,0 and only set their vertices to the desired world space positions.
You would rather want to keep your vertices local like e.g.
// I would use the offset of -0.5f so the mesh is centered at the transform pivot
// Also no need to recreate the arrays everytime, you can simply reference the same ones
private readonly Vector3[] vertices = new Vector3[4]
{
new Vector3(-0.5f, 0, -0.5f);
new Vector3(-0.5f, 0, 0.5f);
new Vector3(0.5f, 0, -0.5f);
new Vector3(0.5f, 0, 0.5f);
};
private readonly int[] tris = new int[] { 0, 1, 2, 1, 3, 2 };
and then in your method do
GameObject tileObject = new GameObject($"{x},{y}");
tileObject.transform.parent = transform;
tileObject.localScale = new Vector3 (tileSize, 1, tileSize);
tileObject.localPosition = new Vector3(x * tileSize, 0, y * tileSize);
The latter depends of course on your needs. Actually I would prefer to have the tiles also centered around the grid object so something like e.g.
// The "-0.5f" is for centering the tile itself correctly
// The "-gridWith/2f" makes the entire grid centered around the parent
tileObject.localPosition = new Vector3((x - 0.5f - gridWidth/2f) * tileSize, 0, (y - 0.5f - gridHeight/2f) * tileSize);
In order to later find out which tile you are standing on (e.g. via raycasts, collisions, etc) I would then rather use a dedicated component and simply tell it it's coordinates like e.g.
// Note that Tile is a built-in type so you would want to avoid confusion
public class MyTile : MonoBehaviour
{
public Vector2Int GridPosition;
}
and then while generating your grid you would simply add
var tile = tileObject.AddComponent<MyTile>();
tile.GridPosition = new Vector2Int(x,y);
while you can still also access its transform.position to get the actual world space center of the tiles

How can I find the volume and vertices of the intersection between two axis aligned cubes?

how can I find the volume of the intersection of two cubes aligned to the axis?
The cubes can have different sizes and positions.
(I add a picture to show a simple example case of two cubes)
After deep research, I am quite sure that does not exist a specific function in Unity to use for this purpose, the only way to work out this problem is mathematical logic.
For example, my first idea was:
To find the 8 vertexes of the cube"intersection" (B in image).
Try to build a new cube with this vertex.
Find the size and volume of cube "intersection".
Unity permits to find:
the centre of each primary cube (A in the image) with "Bounds.centre";
the extents of each primary cube (A in the image) from the centre of the cube (half of the size of the Bounds) with "Bounds.extents".
documentation avaible here:https://docs.unity3d.com/ScriptReference/Bounds.html
However, I can't find a way to have the vertex of each cube.
And, secondly, a logical function that can find what 8 of 16 vertexes found are the right vertex to use to build the cube"Intersection".
Have u got any help or suggest?
Image:
If as you say the cubes are going to be always axis aligned with the Unity world (=> bounds = collider/renderer volume) you could probably simply do something like
take the Bounds of both boxes
take the min and max of these
=> check how much these overlap on each axis individually using the maximum of the mins and the minimum of the maxes
What you get by this are a new min and max point of the overlapping box.
This is enough information
to calculate the volume by multiplying the individual components of the vector between these min and max.
to get all the vertices by getting all permutations between min and max for each axis.
something like
public class OverlapArea
{
public readonly Vector3 min;
public readonly Vector3 max;
public readonly float volume;
public Vector3 frontBottomLeft => min;
public readonly Vector3 frontBottomRight;
public readonly Vector3 frontTopLeft;
public readonly Vector3 frontTopRight;
public readonly Vector3 backBottomLeft;
public readonly Vector3 backBottomRight;
public readonly Vector3 backTopLeft;
public Vector3 backTopRight => max;
public readonly Bounds bounds;
public OverlapArea(Bounds a, Bounds b)
{
// The min and max points
var minA = a.min;
var maxA = a.max;
var minB = b.min;
var maxB = b.max;
min.x = Mathf.Max(minA.x, minB.x);
min.y = Mathf.Max(minA.y, minB.y);
min.z = Mathf.Max(minA.z, minB.z);
max.x = Mathf.Min(maxA.x, maxB.x);
max.y = Mathf.Min(maxA.y, maxB.y);
max.z = Mathf.Min(maxA.z, maxB.z);
frontBottomRight = new Vector3(max.x, min.y, min.z);
frontTopLeft = new Vector3(min.x, max.y, min.z);
frontTopRight = new Vector3(max.x, max.y, min.z);
backBottomLeft = new Vector3(min.x, min.y, max.z);
backBottomRight = new Vector3(max.x, min.y, max.z);
backTopLeft = new Vector3(min.x, max.y, max.z);
// The diagonal of this overlap box itself
var diagonal = max - min;
volume = diagonal.x * diagonal.y * diagonal.z;
bounds.SetMinMax(min, max);
}
public static bool GetOverlapArea(Bounds a, Bounds b, out OverlapArea overlapArea)
{
overlapArea = default;
// If they are not intersecting we can stop right away ;)
if (!a.Intersects(b)) return false;
overlapArea = new OverlapArea(a, b);
return true;
}
}
And so in order to get the overlap information you would do e.g.
// I intentionally used the Bounds as parameters for the method because you can
// either use the Renderer bounds
var boundsA = cubeA.GetComponent<Renderer>().bounds;
// or use Collider bounds in your case they should be equal
var boundsB = cubeB.GetComponent<Collider>().bounds;
if(GetOverlapArea(boundsA, boundsB, out var overlap))
{
// Now in overlap you have all the information you wanted
}
Now in order to actually get the overlap mesh ( if that is where you are going) you have two options
Either use the given edge points and actually create a mesh yourself (note the vertices are in world space so the object should have no scaling itself or on any parent)
...
var mesh = new Mesh
{
vertices = new[]
{
overlapArea.frontBottomLeft,
overlapArea.frontBottomRight,
overlapArea.frontTopLeft,
overlapArea.frontTopRight,
overlapArea.backBottomLeft,
overlapArea.backBottomRight,
overlapArea.backTopLeft,
overlapArea.backTopRight
},
triangles = new[]
{
// Front
0, 2, 1,
1, 2, 3,
// Back
5, 7, 4,
4, 7, 6,
// Left
4, 6, 2,
4, 2, 0,
// Right
1, 7, 5,
1, 3, 7,
// Top
2, 7, 3,
2, 6, 7,
// Bottom
0, 4, 1,
1, 4, 5
}
}
as a little demo
public class Example : MonoBehaviour
{
public Renderer CubeA;
public Renderer CubeB;
public Material overlapMaterial;
private MeshFilter overlap;
private readonly Vector3[] overlapVertices = new Vector3[8];
public void Awake()
{
overlap = new GameObject("Overlap", typeof(MeshRenderer)).AddComponent<MeshFilter>();
var overlapMesh = new Mesh
{
vertices = overlapVertices,
triangles = new[]
{
// Front
0, 2, 1,
1, 2, 3,
// Back
5, 7, 4,
4, 7, 6,
// Left
4, 6, 2,
4, 2, 0,
// Right
1, 7, 5,
1, 3, 7,
// Top
2, 7, 3,
2, 6, 7,
// Bottom
0, 4, 1,
1, 4, 5
}
};
overlap.mesh = overlapMesh;
overlap.GetComponent<Renderer>().material = overlapMaterial;
}
public void Update()
{
if (OverlapArea.GetOverlapArea(CubeA.bounds, CubeB.bounds, out var overlapArea))
{
overlap.gameObject.SetActive(true);
overlap.mesh.vertices = new[]
{
overlapArea.frontBottomLeft,
overlapArea.frontBottomRight,
overlapArea.frontTopLeft,
overlapArea.frontTopRight,
overlapArea.backBottomLeft,
overlapArea.backBottomRight,
overlapArea.backTopLeft,
overlapArea.backTopRight
};
overlap.mesh.RecalculateBounds();
}
else
{
overlap.gameObject.SetActive(false);
}
}
}
or you could instead use an already existing primitive cube (the default Unity cube) and just set it to the correct coordinates and scale it like
overlapVisualizer.transform.position = overlap.bounds.center;
// note we set the local scale so there should be no parent scaling
overlapVisualizer.transform.localScale = overlap.bounds.size;
again a little demo of that
public class Example : MonoBehaviour
{
public Renderer CubeA;
public Renderer CubeB;
public Transform overlap;
public void Update()
{
if (OverlapArea.GetOverlapArea(CubeA.bounds, CubeB.bounds, out var overlapArea))
{
overlap.gameObject.SetActive(true);
overlap.position = overlapArea.bounds.center;
overlap.localScale = overlapArea.bounds.size;
}
else
{
overlap.gameObject.SetActive(false);
}
}
}

How to check if device has been rotated on all axis in Unity

I want to check in Unity if the device has been rotated on all of it's axis.
So, I am reading the rotation of all the axis.
What should I do in order to validate for example that the user has "flipped" his device over the X-axis? I need to check the value, and see that they contain 0, 90, 180 and 270 degrees in a loop.
Here is part of my code:
void Update () {
float X = Input.acceleration.x;
float Y = Input.acceleration.y;
float Z = Input.acceleration.z;
xText.text = ((Mathf.Atan2(Y, Z) * 180 / Mathf.PI)+180).ToString();
yText.text = ((Mathf.Atan2(X, Z) * 180 / Mathf.PI)+180).ToString();
zText.text = ((Mathf.Atan2(X, Y) * 180 / Mathf.PI)+180).ToString();
}
The accelerometer only tells you if the acceleration of the device changes. So you will have values if the device started moving, or stopped moving. You can't retrieve its orientation from that.
Instead you need to use the gyroscope of the device. Most device have one nowadays.
Fortunately, Unity supports the gyroscope through the Gyroscope class
Simply using
Input.gyro.attitude
Will give you the orientation of the device in space, in the form of a quaternion.
To check the angles, use the eulerAngles function, for instance, is the device flipped in the x axis:
Vector3 angles = Input.gyro.attitude.eulerAngles;
bool xFlipped = angles.x > 180;
Be careful, you might have to invert some values if you want to apply the rotation in Unity (because it depend which orientation the devices uses for positive values, left or right)
// The Gyroscope is right-handed. Unity is left handed.
// Make the necessary change to the camera.
private static Quaternion GyroToUnity(Quaternion q)
{
return new Quaternion(q.x, q.y, -q.z, -q.w);
}
Here is the full example from the doc (Unity version 2017.3), in case the link above is broken. It shows how to read value from the gyroscope, and apply them to an object in Unity.
// Create a cube with camera vector names on the faces.
// Allow the device to show named faces as it is oriented.
using UnityEngine;
public class ExampleScript : MonoBehaviour
{
// Faces for 6 sides of the cube
private GameObject[] quads = new GameObject[6];
// Textures for each quad, should be +X, +Y etc
// with appropriate colors, red, green, blue, etc
public Texture[] labels;
void Start()
{
// make camera solid colour and based at the origin
GetComponent<Camera>().backgroundColor = new Color(49.0f / 255.0f, 77.0f / 255.0f, 121.0f / 255.0f);
GetComponent<Camera>().transform.position = new Vector3(0, 0, 0);
GetComponent<Camera>().clearFlags = CameraClearFlags.SolidColor;
// create the six quads forming the sides of a cube
GameObject quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
quads[0] = createQuad(quad, new Vector3(1, 0, 0), new Vector3(0, 90, 0), "plus x",
new Color(0.90f, 0.10f, 0.10f, 1), labels[0]);
quads[1] = createQuad(quad, new Vector3(0, 1, 0), new Vector3(-90, 0, 0), "plus y",
new Color(0.10f, 0.90f, 0.10f, 1), labels[1]);
quads[2] = createQuad(quad, new Vector3(0, 0, 1), new Vector3(0, 0, 0), "plus z",
new Color(0.10f, 0.10f, 0.90f, 1), labels[2]);
quads[3] = createQuad(quad, new Vector3(-1, 0, 0), new Vector3(0, -90, 0), "neg x",
new Color(0.90f, 0.50f, 0.50f, 1), labels[3]);
quads[4] = createQuad(quad, new Vector3(0, -1, 0), new Vector3(90, 0, 0), "neg y",
new Color(0.50f, 0.90f, 0.50f, 1), labels[4]);
quads[5] = createQuad(quad, new Vector3(0, 0, -1), new Vector3(0, 180, 0), "neg z",
new Color(0.50f, 0.50f, 0.90f, 1), labels[5]);
GameObject.Destroy(quad);
}
// make a quad for one side of the cube
GameObject createQuad(GameObject quad, Vector3 pos, Vector3 rot, string name, Color col, Texture t)
{
Quaternion quat = Quaternion.Euler(rot);
GameObject GO = Instantiate(quad, pos, quat);
GO.name = name;
GO.GetComponent<Renderer>().material.color = col;
GO.GetComponent<Renderer>().material.mainTexture = t;
GO.transform.localScale += new Vector3(0.25f, 0.25f, 0.25f);
return GO;
}
protected void Update()
{
GyroModifyCamera();
}
protected void OnGUI()
{
GUI.skin.label.fontSize = Screen.width / 40;
GUILayout.Label("Orientation: " + Screen.orientation);
GUILayout.Label("input.gyro.attitude: " + Input.gyro.attitude);
GUILayout.Label("iphone width/font: " + Screen.width + " : " + GUI.skin.label.fontSize);
}
/********************************************/
// The Gyroscope is right-handed. Unity is left handed.
// Make the necessary change to the camera.
void GyroModifyCamera()
{
transform.rotation = GyroToUnity(Input.gyro.attitude);
}
private static Quaternion GyroToUnity(Quaternion q)
{
return new Quaternion(q.x, q.y, -q.z, -q.w);
}
}

First isometric game

I have a problem with my first isometric game. I do not know how to do to my player able to approach the edge of the wall. In this moment player maybe move about in green area.
My map:
int[,] map = new int[,]
{
{1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1, 1}
};
Variables:
int TileWidth = 50;
int TileHeight = 50;
int posX = 2; // map X position
int posY = 2; // map Y position
float playerX = 2 * 50; // player X position
float playerY = 2 * 50; // player Y position
Detect wall:
public bool detectSolidTile(int x, int y)
{
if (map[y, x] == 1) return true; else return false;
}
Movemet:
posX = (int)(Math.Floor((playerX) / 50));
posY = (int)(Math.Floor(playerY / 50));
(...)
if (slide == 1 && !detectSolidTile(posX + 1, posY))
{
playerX++;
}
if (slide == 2 && !detectSolidTile(posX - 1, posY))
{
playerX--;
}
Image -> http://s16.postimg.org/cxkfomemd/tiles.jpg
What I need improve to be able to move from wall to wall?
best regards, Krzysiek
Try making all the map where you can navigate 0, and then the map where you can not 1.
When trying to make a new move check if the future position will be either a 1 or a 0. If it's 0 you let him move, otherwise you stop him.
if (slide == 1 && !detectSolidTile(posX + 1, posY))
{
playerX++;
}
This will detect the map with 1 as being outside the movable region, thus stopping it from going where you want.
Do you understand where the problem is now?
Another solution is to understand the size of the matrix, and as soon x or y reach the maximum value, you stop incrementing them. But if you want to add obstacles later on, keep with what you are doing now, but make sure 0 is for map, 1 is for outside the map.
So here is a edit for BartoszKP:
You might be right that this "!" does not fix his code, I was just explaining how he should do it.
The code I would use for his issues is slightly different.
First of all drop the playerX and playerY since it is the exact same thing as posX and posY, you just do extra calculations.
now this being said you movement algorithm would look something like this:
Variables:
int TileWidth = 50;
int TileHeight = 50;
int posX = 2; // map X position - same thing as playerX
int posY = 2; // map Y position - same thing as playerY
//posX and posY will now hold the position of your player on the map since your conversion from playerX to playerY will only work if you increment a value by 50 or more.
Detect a wall:
//if the tile at coordinates x and y is a wall I return true
//else return false
public bool detectSolidTile(int x, int y)
{
if (map[y, x] == 1) return true;
else return false;
}
Movement:
posX = (int)(Math.Floor((playerX) / 50)); //drop this you don't need it
posY = (int)(Math.Floor(playerY / 50)); //drop this too, you are using posX and posY to store locations
(...)
if (slide == 1 && !detectSolidTile(posX + 1, posY))
{
posX++;
}
if (slide == 2 && !detectSolidTile(posX - 1, posY))
{
posX--;
}
if (slide == 3 && !detectSolidTile(posX, posY+1))
{
posY++;
}
if (slide == 4 && !detectSolidTile(posX, posY-1))
{
posY--;
}
This should work quite well if you use posX and posY as the position of the player on the map.
Making 1 type of coordinate for the player and one type for the map just makes it more confusing at this point. But if you indeed need to use different coordinates for them, you should always refer to playerX and playerY when trying to calculate the movement like this:
if (slide == 1 && !detectSolidTile((int)(Math.Floor((playerX+1) / 50)) + 1, posY))
{
playerX++;
}
And this is because you are changing the value of playerX by 1 not the value of posX, and this will restrict you movement. If this is confusing let me know and I will try to explain this better.

Spiked vertexes on generated Icosphere

I've been working on a program to generate an icosphere. (A sphere with evenly distributed vertexes across the face, to be used for terrain deformation)
I have pretty much everything done, the sphere is generated, subdivided and drawn. The problem I am running into is that somewhere in the code, some of the vertices (the twelve starting vertexes, I believe) are being set to twice the radius, rather than just the radius.
Here are three images, showing the icosphere at zero, one and two refinement passes:
http://i41.photobucket.com/albums/e262/cstgirllover/Cho/Ico0Refinement.png
http://i41.photobucket.com/albums/e262/cstgirllover/Cho/Ico1Refinement.png
http://i41.photobucket.com/albums/e262/cstgirllover/Cho/Ico2Refinement.png
and here is the code that generates the icosahedron, and then breaks it down into the icosphere:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Icosahedron_Test
{
class Icosahedron
{
int radius; // radius of the planet
int refinement; // number of times to refine the traingles
int faces = 20;
Vector3[] basePositions; // Vertex points for three defining rectangles
TriXYZ[] vertices; // Vertex points for triangles which define the spherical surface
public Icosahedron(int tRadius, int tRefinement, TriXYZ[] tVertices)
{
radius = tRadius;
refinement = tRefinement;
vertices = tVertices;
}
public TriXYZ[] InitializeArray()
{
double t = radius*((1+Math.Sqrt(5))/2);
Vector3[] basePositions =
{
//First Rectangle
new Vector3(-radius, (float)t, 0),
new Vector3(radius, (float)t, 0),
new Vector3(-radius, (float)-t, 0),
new Vector3(radius, (float)-t, 0),
//Seconds Rectangle
new Vector3(0, -radius, (float)t),
new Vector3(0, radius, (float)t),
new Vector3(0, -radius, (float)-t),
new Vector3(0, radius, (float)-t),
//Third Rectangle
new Vector3((float)t, 0, -radius),
new Vector3((float)t, 0, radius),
new Vector3((float)-t, 0, -radius),
new Vector3((float)-t, 0, radius)
};
TriXYZ[] vertices =
{
new TriXYZ(basePositions[5], basePositions[11], basePositions[0], 1),
new TriXYZ(basePositions[1], basePositions[5], basePositions[0], 1),
new TriXYZ(basePositions[7], basePositions[1], basePositions[0], 1),
new TriXYZ(basePositions[10], basePositions[7], basePositions[0], 1),
new TriXYZ(basePositions[11], basePositions[10], basePositions[0], 1),
new TriXYZ(basePositions[9], basePositions[5], basePositions[1], 1),
new TriXYZ(basePositions[4], basePositions[11], basePositions[5], 1),
new TriXYZ(basePositions[2], basePositions[10], basePositions[11], 1),
new TriXYZ(basePositions[6], basePositions[7], basePositions[10], 1),
new TriXYZ(basePositions[8], basePositions[1], basePositions[7], 1),
new TriXYZ(basePositions[4], basePositions[9], basePositions[3], 1),
new TriXYZ(basePositions[2], basePositions[4], basePositions[3], 1),
new TriXYZ(basePositions[6], basePositions[2], basePositions[3], 1),
new TriXYZ(basePositions[8], basePositions[6], basePositions[3], 1),
new TriXYZ(basePositions[9], basePositions[8], basePositions[3], 1),
new TriXYZ(basePositions[5], basePositions[9], basePositions[4], 1),
new TriXYZ(basePositions[11], basePositions[4], basePositions[2], 1),
new TriXYZ(basePositions[10], basePositions[2], basePositions[6], 1),
new TriXYZ(basePositions[7], basePositions[6], basePositions[8], 1),
new TriXYZ(basePositions[1], basePositions[8], basePositions[9], 1),
};
return vertices;
}
public TriXYZ[] Refine(TriXYZ[] rVertices, int rRefinement, float radius)
{
TriXYZ[] tVertices; // Temp list of triangles
Vector3 vertex1; // position of first vertex of base triangle
Vector3 vertex2; // position of second vertex of base triangle
Vector3 vertex3; // position of third vertex of base triangle
int tDepth; // depth of the current triangle
//int listPos = 0; // base list position integer
int nListPos = 0; // new list position integer
int cRefine = 0; // current refinement iteration
while(cRefine < rRefinement) // loop until the icosphere has been refined the inputted number of times
{
tVertices = new TriXYZ[20 + (4*rVertices.Length)]; // make the temporary list empty, and long enough for the original 20 triangles, plus four per triangle for each level of refinement.
for (int listPos = 0; listPos < rVertices.Length; listPos++ ) // Loop through every triangle in the list
{
TriXYZ cTriangle = rVertices[listPos];
tDepth = cTriangle.GetDepth;
vertex1 = cTriangle.GetVertex1; // point 0
vertex2 = cTriangle.GetVertex2; // point 1
vertex3 = cTriangle.GetVertex3; // point 2
if (tDepth == cRefine + 1) // if the depth of this triangle in the list equals the current refinement iteration;
// depth one for first refinement pass, depth two for second, etc; subdivide the triangle
// This prevents unnecessarily re-refining old triangles
{
TriXYZ[] parts = new TriXYZ[5];
parts = cTriangle.subDivide(radius);
tVertices[nListPos] = parts[0]; // Put the original larger triangle at the front if the list
tVertices[nListPos + 1] = parts[1]; // First subdivided triangle
tVertices[nListPos + 2] = parts[2]; // Second subdivided triangle
tVertices[nListPos + 3] = parts[3]; // Third subdivided triangle
tVertices[nListPos + 4] = parts[4]; // Fourth subdivided triangle
nListPos = nListPos + 5; // Move forward in the new triangle list so the next set of triangles doesn't overwrite this set.
}
else if (tDepth < cRefine + 1) // Ifthe triangle's depth is less than the current refinement iteration (depth 1 on refinement 2) then add the current triangle to the new list at nListPos
{
tVertices[nListPos] = new TriXYZ(vertex1, vertex2, vertex3, tDepth);
nListPos++;
}
// it shouldn't be possible for the tDepth to be greater than cRefine
} // end for loop: either move to the next triangel in the original list, or move on to the next level of refinement
rVertices = tVertices; // Replace the old list with the new one, so that the next time it
// runs through the refinement process, it will refine the new
// traingles
cRefine++; // increase refinement interation variable so that it will either refine the next set of triangles, or exit the refinement loop.
nListPos = 0; // reset the new list position integer so it overwrites the exiting data
} // end while loop: either move on to the next refinement set, or exit the loop
vertices = rVertices; // make sure the class=level vertices
return rVertices;
} // End Refinement Class
public int Length
{
get { return vertices.Length; }
private set { }
}
public VertexPositionColor[] BuildList(TriXYZ[] tList, int tDepth)
{
VertexPositionColor[] finalList = new VertexPositionColor[tList.Length*3]; // final list to be returned for drawing
int listPos = 0; // current position in the final list (where the vector 3 is being applied)
Vector3 pos1; // Vertex 1 position of TriXYZ triangle
Vector3 pos2; // Vertex 2 position of TriXYZ triangle
Vector3 pos3; // Vertex 3 position of TriXYZ triangle
int depth;
for(int cTri = 0; cTri<tList.Length; cTri+=1) // Loop through the TriXYZ list and get all the vertexes from it, then apply them to the final draw list
{
pos1 = tList[cTri].GetVertex1;
pos2 = tList[cTri].GetVertex2;
pos3 = tList[cTri].GetVertex3;
depth = tList[cTri].GetDepth;
if (depth == tDepth)
{
finalList[listPos] = new VertexPositionColor(pos1, Color.Blue);
finalList[listPos + 1] = new VertexPositionColor(pos2, Color.Red);
finalList[listPos + 2] = new VertexPositionColor(pos3, Color.Green);
listPos = listPos + 3;
}
}
return finalList;
}
}
}
and here is the TriXYZ class, that holds the triangle data:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Icosahedron_Test
{
class TriXYZ
{
Vector3 vertex1;
Vector3 vertex2;
Vector3 vertex3;
int depth;
float material1; // float for first material value amount (in %) deals with blending
float material2; // float for second material value amount (in %) deals with blending
public TriXYZ(Vector3 pos1, Vector3 pos2, Vector3 pos3, int tDepth)
{
vertex1 = pos1;
vertex2 = pos2;
vertex3 = pos3;
depth = tDepth;
}
public TriXYZ(Vector3 pos1, Vector3 pos2, Vector3 pos3, int tDepth, float tMaterial1, float tMaterial2)
{
vertex1 = pos1;
vertex2 = pos2;
vertex3 = pos3;
depth = tDepth;
material1 = tMaterial1;
material2 = tMaterial2;
}
// public access to triangle data, read-write
public Vector3 GetVertex1
{
get { return vertex1; }
set { vertex1 = value; }
}
public Vector3 GetVertex2
{
get { return vertex2; }
set { vertex2 = value; }
}
public Vector3 GetVertex3
{
get { return vertex3; }
set { vertex3 = value; }
}
public int GetDepth
{
get { return depth; }
set { depth = value; }
}
public static Vector3 Midpoint(Vector3 pos1, Vector3 pos2, float radius)
{
Vector3 midpoint; // returned midpoint between the two inputted vectors
float x;
float y;
float z;
x = (pos1.X + pos2.X)/2;
y = (pos1.Y + pos2.Y)/2;
z = (pos1.Z + pos2.Z)/2;
midpoint = new Vector3(x, y, z);
midpoint.Normalize();
midpoint = midpoint * radius;
return midpoint;
}
public TriXYZ[] subDivide(float radius)
{
Vector3 r; // placeholder for new vertex position, aligned to planet sphere radius
Vector3 UV; // new vector position
TriXYZ[] nTriangle = new TriXYZ[5]; // array of triangle values to return
Vector3 mid1 = Midpoint(vertex1, vertex2, radius);
Vector3 mid2 = Midpoint(vertex2, vertex3, radius);
Vector3 mid3 = Midpoint(vertex3, vertex1, radius);
nTriangle[0] = new TriXYZ(vertex1, vertex2, vertex3, depth); // Put the original larger triangle at the front if the list
nTriangle[1] = new TriXYZ(vertex1, mid1, mid3, depth + 1); // First subdivided triangle
nTriangle[2] = new TriXYZ(mid1, vertex2, mid2, depth + 1); // Second subdivided triangle
nTriangle[3] = new TriXYZ(mid3, mid2, vertex3, depth + 1); // Third subdivided triangle
nTriangle[4] = new TriXYZ(mid3, mid1, mid2, depth + 1); // Fourth subdivided triangle
return nTriangle;
}
}
}
Any help will be greatly appreciate. I imagine it's something simple, I just cannot seem to find the problem.
It's definitely the initial vectors. If memory serves (it's been a while since I've dealt with icosahedrons, etc.), you just use the golden ratio to create one with and edge length of 2 (which you're doing). Perhaps normalize the vectors before multiplying by the radius? The reason I say that is because those vertices never get updated in your code, so it has to be the initial values (unless of course I missed something, which is possible).

Categories

Resources