I am attempting to cut a loaded 3D model in half using MonoGame (which is extremely similar XNA). I do not need to do this in real time so performance is not a huge issue.
I get my vertices and indices using the ModelMeshPart as such.
Vector3[] vertices = new Vector3[part.NumVertices];
part.VertexBuffer.GetData<Vector3>(vertices);
short[] indices = new short[part.PrimitiveCount * 3];
part.IndexBuffer.GetData<short>(indices);
and then set them using
part.IndexBuffer.SetData<Vector3>(vertices);
part.VertexBuffer.SetData<short>(indices);
Prior to that though I take those arrays and try to empty out all vertices (and indices that refer to them) that are positioned behind the center Z location of the model as such.
float centerZ = modelMesh.BoundingSphere.Center.Z;
for (int i = 0; i < indices.Length; i += 3)
{
short index0 = indices[i];
short index1 = indices[i + 1];
short index2 = indices[i + 2];
Vector3 vert0 = vertices[index0];
Vector3 vert1 = vertices[index1];
Vector3 vert2 = vertices[index2];
if (vert0.Z > centerZ && vert1.Z > centerZ && vert2.Z > centerZ)
{
vert0 = Vector3.Zero;
vert1 = Vector3.Zero;
vert2 = Vector3.Zero;
indices[i] = short.MinValue;
indices[i + 1] = short.MinValue;
indices[i + 2] = short.MinValue;
}
}
but in the end I get something that looks like this rather than a model cut in half. I am completely new to games programming and my comprehension of vertices and indices is still extremely poor. Obviously I am missing something really fundamental, any help would be sincerely appreciated.
short.MinValue is -32767 since short is signed, so you shouldn't be using that, since the indice value for the vertice can't be negative. You should use = 0 or ushort.MinValue. Also keep in mind that this method will not cut the model in half perfectly, triangles perpendicular to the center will still remain.
You can try something like this:
if (vert0.Z <= centerZ || vert1.Z <= centerZ || vert2.Z <= centerZ)
{
if (vert0.Z > centerZ) vert0.Z = centerZ;
if (vert1.Z > centerZ) vert1.Z = centerZ;
if (vert2.Z > centerZ) vert2.Z = centerZ;
}
else
{
indices[i] = 0;
indices[i + 1] = 0;
indices[i + 2] = 0;
}
Edit:
also this part looks wrong:
part.IndexBuffer.SetData<short>(vertices);
part.VertexBuffer.SetData<Vector3>(indices);
you should pass vertices to the VertexBuffer and indices to the IndexBuffer. And you should probably use ushort on indices, since theres no reason a indice would be negative.
Related
i have somewhat implemented marching cubes in unity/c# (you dont need to know unity to help me though) and i cant stop feeling like i have made a big mistake in my code because it is so slow. i am already running it on a separate thread but it just takes ages to complete. please help me optimize my code.
private void _UpdateChunk()
{
lock (this)
{
// clear the tri, vert and uv lists
ClearMeshData();
// Loop through each "cube" in the terrain.
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
for (int z = 0; z < width; z++)
{
// Create an array of floats representing each corner of a cube and get the value from our terrainMap.
float[] cube = new float[8];
float[] strengths = new float[8];
for (int i = 0; i < 8; i++)
{
Vector3Int corner = new Vector3Int(x, y, z) + gamedata.CornerTable[i];
cube[i] = terrainMap[corner.x, corner.y, corner.z].BlockType;
strengths[i] = terrainMap[corner.x, corner.y, corner.z].Strength;
}
// Pass the value into the MarchCube function.
MarchCube(new Vector3(x, y, z), cube, strengths);
}
}
}
}
}
void MarchCube(Vector3 position, float[] cube, float[] strengths)
{
// Get the configuration index of this cube.
int configIndex = GetCubeConfiguration(cube);
// If the configuration of this cube is 0 or 255 (completely inside the terrain or completely outside of it) we don't need to do anything.
if (configIndex == 0 || configIndex == 255)
return;
// Loop through the triangles. There are never more than 5 triangles to a cube and only three vertices to a triangle.
int edgeIndex = 0;
Vector3 vert1 = new Vector3();
Vector3 vert2 = new Vector3();
float vert1sample = 0;
float vert2sample = 0;
float lerp = 0;
int indice = 0;
for (int i = 0; i < 5; i++)
{
for (int p = 0; p < 3; p++)
{
// Get the current indice. We increment triangleIndex through each loop.
indice = gamedata.TriangleTable[configIndex, edgeIndex];
// If the current edgeIndex is -1, there are no more indices and we can exit the function.
if (indice == -1)
return;
// Get the vertices for the start and end of this edge.
vert1 = position + gamedata.EdgeTable[indice, 0];
vert2 = position + gamedata.EdgeTable[indice, 1];
vert1sample = strengths[gamedata.EdgeIndexTable[indice, 0]];
vert2sample = strengths[gamedata.EdgeIndexTable[indice, 1]];
// Get the midpoint of this edge.
lerp = Mathf.Abs(vert1sample) / (Mathf.Abs(vert2sample) + Mathf.Abs(vert1sample));
Vector3 vertPosition = Vector3.Lerp(vert1, vert2, lerp);
// Add to our vertices and triangles list and incremement the edgeIndex.
vertices.Add(vertPosition);
triangles.Add(vertices.Count - 1);
if (getChunkVoxel(vert1 + chunkPosition) != 0)
{
uvs.Add(new Vector2(getChunkVoxel(vert1 + chunkPosition) - 1, 0));
}
else
{
uvs.Add(new Vector2(getChunkVoxel(vert2 + chunkPosition) - 1, getChunkVoxel(vert2 + chunkPosition) - 1));
}
edgeIndex++;
}
}
}
int GetCubeConfiguration(float[] cube)
{
// Starting with a configuration of zero, loop through each point in the cube and check if it is below the terrain surface.
int configurationIndex = 0;
for (int i = 0; i < 8; i++)
{
// If it is, use bit-magic to the set the corresponding bit to 1. So if only the 3rd point in the cube was below
// the surface, the bit would look like 00100000, which represents the integer value 32.
if (cube[i] < terrainSurface)
configurationIndex |= 1 << i;
}
return configurationIndex;
}
it appears that this is the part that slows my game down, help would be appreciated
i already made it faster by changing terrainpoint from a class to a struct but it is still very slow.
One main reason it is slow is that there is a lot of allocations in the loop putting a lot of pressure on the garbadge collector. There is currently 11 allocation per "cube" in the terrain in _UpdateChunk and up to 17 in MarchCube (possibly even more if the expressions like position + gamedata.EdgeTable[indice, 0] allocates a new vector). This is not reasonable. Many allocation are not needed. For example cube and strengths can be preallocated once for all the cubes in the beginning of _UpdateChunk. You do not need to allocate the vector in the expression to compute corner: you can just compute the components separately manually (or you can possibly preallocate the vector and reset its component when needed). The same thing applies for the new Vector3(x, y, z) can can be preallocated and set in the loop. Such an algorithm is computationally intensive so you should get away any overhead like virtual method calls and allocations/GC -- only low-level arrays accesses and mathematical operations should remains.
Note that some computations can be optimized. For example GetCubeConfiguration can be modified so to be branchless. Mathf.Abs(vert1sample) can be precomputed so not to compute it twice (though the compiler may already do that). I am also wondering if the expression like vertices.Add are efficient but this is dependent of the type of container which is not provided here.
I'm learning about Bezier curves and would like to parameterize the equations for distance using an estimation method. So far, my code seems to work for single points (EG Bezier(start=0, mid=1, end=5, nPoints=6) yields [0 1 2 3 4 5]). However, when I attempt to apply this to multi-dimensional curves, my results are not as expected.
C# code (executed in Unity for visualization). The function (should) get a point on the curve (defined by the points pts) at a length l% of the length.
Vector3 BezierL(Vector3[] pts, float l)
{
int i;
float[] tVals = new float[n];
Vector3[] points = new Vector3[n];
float[] cumDist = new float[n];
for (i = 1; i < n; i++)
{
tVals[i] = i / (float)(n - 1);
points[i] = Bezier(pts, tVals[i]);
cumDist[i] = cumDist[i - 1] +
(points[i] - points[i - 1]).magnitude;
}
// Interpolate to estimate t
float targetLen = l * cumDist[n - 1];
int ind = Array.BinarySearch(cumDist, targetLen);
if (ind < 0)
ind = ~ind;
float t = Mathf.Lerp(tVals[ind - 1], tVals[ind],
(targetLen - cumDist[ind - 1]) / (cumDist[ind] - cumDist[ind - 1]));
return Bezier(pts, t);
}
where Bezier(Vector3[] pts, t) gets a point on the curve defined by pts at time t. For whatever reason, this partially works in that all points are equally spaced, but some points are stacked at the initial point rather than being distributed along the curve.
This was my reference for developing this algorithm, so I'm unsure if my implementation is incorrect, or if it only applies to lower-dimensional curves.
Thanks in advance!
Oof how embarrassing, I just forgot to compute the 0th point!
I am using Unity 5 to create an isometric game. I have generated a grid of tiles and it works well. However, when I use two different tiles to fill in the grid (their image sizes are slightly different), I get gaps in between the tiles. The obvious solution would be to create the tiles so that they are all the same image size, but this would prevent me from creating anything on a tile that is larger than the size of a tile (eg. a tree).
Here are some images to demonstrate:
With only one type of tile:
With two types of tile:
This is the code I use to create the map:
private void CreateMap() {
float tileWidth;
float tileHeight;
int orderInLayer = 0;
SpriteRenderer r = floorTiles [0].GetComponent<SpriteRenderer> ();
tileWidth = r.bounds.max.x - r.bounds.min.x;
tileHeight = r.bounds.max.y - r.bounds.min.y;
for (int i = 0; i < map.GetLength(0); i++) {
orderInLayer += 1;
for (int j = 0; j < map.GetLength (1); j++) {
Vector2 position = new Vector2 ((j * tileWidth / 2) + (i * tileWidth / 2) + (tileWidth / 2), (j * tileHeight / 2) - (i * tileHeight / 2) + (tileHeight/ 2));
r = map[i,j].GetComponent<SpriteRenderer>();
r.sortingOrder = orderInLayer;
Instantiate(map[i, j], position, Quaternion.identity);
}
}
}
Any help would be greatly appreciated, I cannot seem to fix it!
You appear to be calculating a position for each of your tiles from scratch every time you create one. If you have 2 different sized tiles, then your calculation comes out different, hence the gaps in your tiles. This is because you're only using the width/height of the current tile, failing to take into account any previous tiles that may be a shorter/longer height/width.
Given you have varying heights AND widths you'll need a way to calculate the correct position for both to prevent gaps in the X and Y direction. I've mocked up something here, but it's untested. More of a concept(?) I guess.
float tileHeight = 0;
float tileWidth = 0;
Vector2 position = new Vector2(0,0);
Dictionary<int, float> HeightMap = new Dictionary<int, float>();
for (int iRow = 0; iRow < map.GetLength(0); iRow++)
{
position.x = 0;
orderInLayer += 1;
for (int jColumn = 0; jColumn < map.GetLength (1); jColumn++)
{
position.y = HeightMap[jColumn];
r = map[iRow, jColumn].GetComponent<SpriteRenderer>();
tileWidth = r.bounds.max.x - r.bounds.min.x;
tileHeight = r.bounds.max.y - r.bounds.min.y;
r.sortingOrder = orderInLayer;
position.x += tileWidth / 2;
position.y += tileHeight / 2;
Instantiate(map[iRow, jColumn], position, Quaternion.identity);
HeightMap[jColumn] = position.y;
}
}
I leave the best way of storing the height, or instantiating the contents of the HeightMap dictionary to however you see fit.
So, this is an interesting problem. I'm making a Bejeweled 3 clone with extra stuff, and I can't figure out how to make the gems above a flame gem explosion move upwards; the middle column moves upward farther than the outer two columns(a normal flame gem explosion is 3x3).
Here is the code I have so far:
for (int gy = i - 1; gy <= i + 1; gy++)
{
if (gy >= 0 && gy < gems.GetLength(0))
{
for (int gx = j; gx >= 0; gx--)
{
if (gy == i)
{
gems[gy, gx].MoveTowardsPosition(gems[gx, gy].Position.Swap() + new Vector2(0, -70), gemMoveSpeed * 1.8f, true, softMove: true);
}
else gems[gy, gx].MoveTowardsPosition(gems[gx, gy].Position.Swap() + new Vector2(0, -45), gemMoveSpeed * 1.8f, true, softMove: true);
}
}
}
The MoveTowardsPosition() method works fine in other places. This is not the problem. What the problem is is that I can't get the gems to move upwards(and in the correct location; i.e, they either move sideways, oddly, or upwards but in the wrong position. Also, i and j are the coordinates of the center of the flame gem explosion.
The other thing I should probably mention is that the visual position is separate from the position in the Gem array (gems[,]), and that the visual position is y, x whereas the gem array is x, y. I don't know how this arose, but it would be quite intensive to fix it.
Oh, and the Swap() extension method simply swaps the values in a Vector2.
If you need more information, just say so. This is my first time posting a question here, so if I've missed something, please tell me.
Okay, so I finally figured it out. Yay for persistence! Here's the code:
for (int gy = j - 1; gy <= j + 1; gy++)
{
if (gy >= 0 && gy < gems.GetLength(0))
{
for (int gx = i; gx >= 0; gx--)
{
if (gy == j)
{
gems[gx, gy].MoveTowardsPosition(gems[gy, gx].Position.Swap() + new Vector2(0, -70), gemMoveSpeed * 1.8f, true, softMove: true);
}
else gems[gx, gy].MoveTowardsPosition(gems[gy, gx].Position.Swap() + new Vector2(0, -45), gemMoveSpeed * 1.8f, true, softMove: true);
moveTimer = 0;
}
}
}
Turned out I had to not only reverse the gy and gx, but ALSO the i and j. I haven't fixed the swapped visual position/array position yet. I might do that in the future.
At last I have something displayed. Switched to using graphics.GraphicsDevice.DrawIndexedPrimitives ... Next problem... Only one triangle is displayed. The data set is about 200 triangles. I formatted the data coming in to make sure every three consecutive vectors form a triangle face. These are irregular triangles forming an irregular shape. I don't fully understand the indexing of the vertices. Looks like each 3 indices form a triangle. If that is so then the indices match the data coming in. I did this:
int i4 = -1;
indices = new int[xData1.Count];
for (int i2 = 0; i2 < xData1.Count; i2++)
{
i4++;
cubeVertices[i4].Position = new Vector3((float)xData1[i2][0], (float)xData1[i2][1], (float)xData1[i2][2]);
cubeVertices[i4].Color = Color.LawnGreen;
indices[i4] = i4;
}
making the indices match the vertices coming in.. then I used Reimers normal calc to provide normals.. this is probably wrong as his example was using 6 vertices per index (I think!), like this:
for (int i = 0; i < cubeVertices.Length; i++)
cubeVertices[i].Normal = new Vector3(0, 0, 0);
for (int i = 0; i < indices.Length / 3; i++)
{
int index1 = indices[i * 3];
int index2 = indices[i * 3 + 1];
int index3 = indices[i * 3 + 2];
Vector3 side1 = cubeVertices[index1].Position - cubeVertices[index3].Position;
Vector3 side2 = cubeVertices[index1].Position - cubeVertices[index2].Position;
Vector3 normal = Vector3.Cross(side1, side2);
cubeVertices[index1].Normal += normal;
cubeVertices[index2].Normal += normal;
cubeVertices[index3].Normal += normal;
}
for (int i = 0; i < cubeVertices.Length; i++)
cubeVertices[i].Normal.Normalize();
how many things do I need to fix here? I am only seeing 1 out of a couple of hundred triangles
:(
thx for your patience
public struct VertexPositionColorNormal
{
public Vector3 Position;
public Color Color;
public Vector3 Normal;
public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
(
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0),
new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
);
}
...
private void CopyToBuffers()
{
vertexBuffer = new VertexBuffer(graphics.GraphicsDevice, VertexPositionColorNormal.VertexDeclaration,
cubeVertices.Length, BufferUsage.WriteOnly);
vertexBuffer.SetData(cubeVertices);
myIndexBuffer = new IndexBuffer(graphics.GraphicsDevice, typeof(int), indices.Length, BufferUsage.WriteOnly);
myIndexBuffer.SetData(indices);
}
....
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
basicEffect.World = world;
basicEffect.View = view;
basicEffect.Projection = proj;
pass.Apply();
graphics.GraphicsDevice.Indices = myIndexBuffer;
graphics.GraphicsDevice.SetVertexBuffer(vertexBuffer);
graphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
cubeVertices.Length, 0, indices.Length / 3);
Your normal calculation is correct, and even if it was wrong the only thing that would happen is that your triangles would receive the wrong lightning.
You're using indices which exactly match the order of the vertices coming in, which is in itself redundant. If you switch to not setting the indices at all and use DrawPrimitives instead with the primitive count the same does that make a difference?
Other than that, are you sure that the data you're giving it is valid? Are the vertex positions correctly set?