//caxo - corner A x offset //cbyo - corner B y offset, etc //should be faster to just use ints instead of passing in vec3's, might be negligicable, but isn't too much more compliated /// <summary> /// Checks the edge created beteween the two points xyz+ cornerAXYZoffset and xyz + cornerBXYZoffset for a sign change between the densities of the corners /// if there is a sign change the edge is added to a list and the neighbouring cells that share this edge have the edge added to their QEF solver /// </summary> public void CheckEdge(int x, int y, int z, int caxo, int cayo, int cazo, int cbxo, int cbyo, int cbzo) { GridCorner cA = corners[x + caxo, y + cayo, z + cazo]; GridCorner cB = corners[x + cbxo, y + cbyo, z + cbzo]; List <GridCell> facesTemp = new List <GridCell>(); if (cA.density * cB.density <= 0f) //different sign { GridEdge e = new GridEdge(cA.position, cB.position); int intersectionSteps = 8; if (subdivisionLevel == 1) { intersectionSteps = 16; } e.intersection = ApproximateEdgeIntersection(cA.position, cB.position, Density, intersectionSteps); e.normal = CalculateSurfaceNormal(e.intersection, Density); //e.intersection = e.normal = Vector3.zero; edges.Add(e); #region swapping and comments //need to find every cell that shares this edge so we can do QEF solve stuff per cell //get start corner indicies, sorted by smallest corner first //then find the direction we're going on (offset from start to end) //then just manually find neighbours #region swapping logic //swap so they're ordred properly //could do this in the density block only when we need to bool swap = false; if (x + caxo < x + cbxo) { //good } else if (x + caxo == x + cbxo) //same check next { if (y + cayo < y + cbyo) { //good } else if (y + cayo == y + cbyo) //same, check next { if (z + cazo < z + cbzo) { //good } else if (z + cazo == z + cbzo) { //same...all components were the same.. This should never happen } else { swap = true; } } else { swap = true; } } else { swap = true; } #endregion int sx; int sy; int sz; int dx; int dy; int dz; if (swap) { sx = x + cbxo; sy = y + cbyo; sz = z + cbzo; dx = (caxo - cbxo); dy = (cayo - cbyo); dz = (cazo - cbzo); } else { sx = x + caxo; sy = y + cayo; sz = z + cazo; dx = (cbxo - caxo); dy = (cbyo - cayo); dz = (cbzo - cazo); } #endregion //we should have six directions if (dx == 1 && dy == 0 && dz == 0) { //cells offsets [0,0,0] [0,0,-1] [0,-1,-1] [0,-1,0] //check each offset to see if it exists (and doesn't cause an out of range error) //if it exists add to the QEF and solve and stuff facesTemp.Add(AddToCellQEF(e, sx, sy, sz, 0, 0, 0)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, 0, 0, -1)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, 0, -1, -1)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, 0, -1, 0)); } else if (dx == -1 && dy == 0 && dz == 0) { //this case will never happen, because we sort on x first so that the direction is always+. //If x *was* to be -, we would have swapped corners //but lets just fill it in anyways //cells offsets [-1,0,0] [-1,0,-1] [-1,-1,-1] [-1,-1,0] facesTemp.Add(AddToCellQEF(e, sx, sy, sz, -1, 0, 0)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, -1, 0, -1)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, -1, -1, -1)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, -1, -1, 0)); } else if (dx == 0 && dy == 1 && dz == 0) { //cell offsets [0,0,-1] [0,0,0] [-1,0,0] [-1,0,-1] facesTemp.Add(AddToCellQEF(e, sx, sy, sz, 0, 0, -1)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, 0, 0, 0)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, -1, 0, 0)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, -1, 0, -1)); } else if (dx == 0 && dy == -1 && dz == 0) { //cell offsets [0,-1,0] [0,-1,-1] [-1,-1,-1] [-1,-1,0] facesTemp.Add(AddToCellQEF(e, sx, sy, sz, 0, -1, -1)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, 0, -1, 0)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, -1, -1, 0)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, -1, -1, -1)); } else if (dx == 0 && dy == 0 && dz == 1) { //cell offsets [0,0,0] [0,-1,0] [-1,-1,0] [-1,0,0] facesTemp.Add(AddToCellQEF(e, sx, sy, sz, 0, 0, 0)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, 0, -1, 0)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, -1, -1, 0)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, -1, 0, 0)); } else if (dx == 0 && dy == 0 && dz == -1) { //cell offsets [0,0,-1] [0,-1,-1] [-1,-1,-1] [-1,0,-1] facesTemp.Add(AddToCellQEF(e, sx, sy, sz, 0, 0, -1)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, 0, -1, -1)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, -1, -1, -1)); facesTemp.Add(AddToCellQEF(e, sx, sy, sz, -1, 0, -1)); } if (facesTemp.Count == 4) { facesTemp.Sort(); //need to sort the faces so we don't get any duplicates //and then we need to check to make sure the faces list doesn't contain this face GridCell ft0 = facesTemp[0]; //Debug.Log(ft0 == null); //Debug.Log(ft0.cellIndex == null); Vector3 index = new Vector3(ft0.cellIndex.x, ft0.cellIndex.y, ft0.cellIndex.z); List <GridFace> fl = null; if (faces.ContainsKey(index)) { fl = faces[index]; } //we need to check every element in FL to make sure we don't already have this face if (fl == null) { //cant check because this face doesn't have a spot in the list yet //so we can just add it because it doesn't exist, so this will be the first one fl = new List <GridFace>(); GridFace f = new GridFace(); f.faces.Add(facesTemp[0]); f.faces.Add(facesTemp[1]); f.faces.Add(facesTemp[2]); f.faces.Add(facesTemp[3]); fl.Add(f); faces[index] = fl; } else { if (fl.Count == 0) { //cant check. For some reason the list was created, but never added to //add because it doesn't exist yet GridFace f = new GridFace(); f.faces.Add(facesTemp[0]); f.faces.Add(facesTemp[1]); f.faces.Add(facesTemp[2]); f.faces.Add(facesTemp[3]); fl.Add(f); } else { for (int i = 0; i < fl.Count; i++) { //we can skip checking facesTemp[0].cellIndex against fl[0][0] because we know they're the same //because that's how we sort the main list anyways if (fl[i].faces[1] == facesTemp[1]) { //second cell is the same if (fl[i].faces[2] == facesTemp[2]) { //third cell is the same if (fl[i].faces[3] == facesTemp[3]) { //last cell is the same... all match, so we don't want to add this faceTemp to the real list //because it will be a duplicate } else { //diff GridFace f = new GridFace(); f.faces.Add(facesTemp[0]); f.faces.Add(facesTemp[1]); f.faces.Add(facesTemp[2]); f.faces.Add(facesTemp[3]); fl.Add(f); break; } } else { //diff GridFace f = new GridFace(); f.faces.Add(facesTemp[0]); f.faces.Add(facesTemp[1]); f.faces.Add(facesTemp[2]); f.faces.Add(facesTemp[3]); fl.Add(f); break; } } else { //diff GridFace f = new GridFace(); f.faces.Add(facesTemp[0]); f.faces.Add(facesTemp[1]); f.faces.Add(facesTemp[2]); f.faces.Add(facesTemp[3]); fl.Add(f); break; } } } } } } }
public void Start() { //List<List<List<List<int>>>> t = new List<List<List<List<int>>>>(); //t[10][10][10] = new List<int>(); //int[,,] t = new int[,,]; //t[0, 0, 0] = 10; //given three ints, we return a list of faces that have those three ints as their sorted first face. //We need to be able to index the list directly with those three ints quickly. //we cant set the size at start because we don't know the max size of the [x,y,z] //t[x,y,z] -> List<GridFace> where GridFace f.faces[0].cellIndex.xyz == xyz //t[10,1000,10] needs to work so we can put a list there if it doesn't exist //t[10,1001,10] needs to work lastSubdivions = subdivisionLevel; subdivisions = (subdivisionLevel == 0) ? 1 : (int)Mathf.Pow(2, subdivisionLevel); cells = new GridCell[subdivisions, subdivisions, subdivisions]; corners = new GridCorner[subdivisions + 1, subdivisions + 1, subdivisions + 1]; edges = new List <GridEdge>(); faces = new Dictionary <Vector3, List <GridFace> >(); for (int x = 0; x < subdivisions; x++) { for (int y = 0; y < subdivisions; y++) { for (int z = 0; z < subdivisions; z++) { cells[x, y, z] = new GridCell(new Vector3(x, y, z), volumeSize, subdivisions, this.transform.position); } } } //should we find the corners and store them here? with refs to the gridCells so we don't get duplicates? We need to be able to //access neighbours to share edges, so it would make sense to do it outside. //this loop could be combined with with the loop above, if we modify it to use previous entries instead of next entires, right? //maybe. Try it later if it's a bottleneck for (int x = 0; x < subdivisions + 1; x++) { for (int y = 0; y < subdivisions + 1; y++) { for (int z = 0; z < subdivisions + 1; z++) { //for every grid cell, create it's corners and store them in the corners[,,] list. //Also grab the neighbour cells that share that corner and give them that index too bool lx = (x == subdivisions ? true : false); //last x index, we need to switch to the +1 edge bool ly = (y == subdivisions ? true : false); bool lz = (z == subdivisions ? true : false); int xo = 0; int yo = 0; int zo = 0; Vector3 cornerOff = cornerOffsets[0]; if (lx) { xo = -1; cornerOff.x = 1; } if (ly) { yo = -1; cornerOff.y = 1; } if (lz) { zo = -1; cornerOff.z = 1; } GridCell cell = cells[x + xo, y + yo, z + zo]; GridCorner corner = new GridCorner(new Vector3(x, y, z), cell.center + (cell.cellSize * cornerOff * 0.5f)); corner.density = Density(corner.position); corners[x, y, z] = corner; } } } Debug.Log("Total Corners: " + corners.Length); for (int x = 0; x < subdivisions; x++) { for (int y = 0; y < subdivisions; y++) { for (int z = 0; z < subdivisions; z++) { //this is to smartly add the edges to a list, to make sure we don't have duplicates. //it's gross, but it should be pretty fast //far-end corner (x) and the edges created with it's neighbours (n) // n____X // / /| // .____n | // | | n // .____./ CheckEdge(x, y, z, 0, 1, 1, 1, 1, 1); CheckEdge(x, y, z, 1, 0, 1, 1, 1, 1); CheckEdge(x, y, z, 1, 1, 0, 1, 1, 1); //need every permutation of x/y/z with any 0 component //100, 010, 001 //110, 101,011 //this was all figured out with trial and error //depedngin on which edge we're on (and if it's the first corner) we fill in the first half of the edges, //then on the next iteration we add on the next HALF, (as shown in the diagram above) //so that we don't get any duplicate edges (like in a swapped order) if (x == 0 && y == 0 && z == 0) { CheckEdge(x, y, z, 0, 0, 0, 0, 0, 1); CheckEdge(x, y, z, 0, 0, 0, 0, 1, 0); CheckEdge(x, y, z, 0, 0, 0, 1, 0, 0); CheckEdge(x, y, z, 0, 1, 1, 0, 0, 1); CheckEdge(x, y, z, 1, 0, 1, 0, 0, 1); CheckEdge(x, y, z, 0, 1, 0, 0, 1, 1); CheckEdge(x, y, z, 0, 1, 0, 1, 1, 0); CheckEdge(x, y, z, 1, 0, 0, 1, 0, 1); CheckEdge(x, y, z, 1, 0, 0, 1, 1, 0); } else if (x != 0 && y == 0 && z == 0) { CheckEdge(x, y, z, 1, 0, 0, 1, 0, 1); CheckEdge(x, y, z, 1, 0, 0, 1, 1, 0); CheckEdge(x, y, z, 1, 0, 0, 0, 0, 0); CheckEdge(x, y, z, 1, 0, 1, 0, 0, 1); CheckEdge(x, y, z, 1, 1, 0, 0, 1, 0); } else if (x == 0 && y != 0 && z == 0) { CheckEdge(x, y, z, 0, 1, 0, 0, 0, 0); CheckEdge(x, y, z, 0, 1, 1, 0, 0, 1); CheckEdge(x, y, z, 0, 1, 0, 0, 1, 1); CheckEdge(x, y, z, 0, 1, 0, 1, 1, 0); CheckEdge(x, y, z, 1, 0, 0, 1, 1, 0); } else if (x == 0 && y == 0 && z != 0) { CheckEdge(x, y, z, 0, 0, 0, 0, 0, 1); CheckEdge(x, y, z, 0, 1, 1, 0, 0, 1); CheckEdge(x, y, z, 1, 0, 1, 0, 0, 1); CheckEdge(x, y, z, 0, 1, 0, 0, 1, 1); CheckEdge(x, y, z, 1, 0, 0, 1, 0, 1); } else if (x != 0 && y == 0 & z != 0) { CheckEdge(x, y, z, 1, 0, 0, 1, 0, 1); CheckEdge(x, y, z, 1, 0, 1, 0, 0, 1); } else if (x != 0 && y != 0 && z == 0) { CheckEdge(x, y, z, 1, 1, 0, 1, 0, 0); CheckEdge(x, y, z, 0, 1, 0, 1, 1, 0); } else if (x == 0 && y != 0 && z != 0) { CheckEdge(x, y, z, 0, 1, 1, 0, 0, 1); CheckEdge(x, y, z, 0, 1, 0, 0, 1, 1); } } } } Debug.Log("Total Edges: " + edges.Count); //create corners for every cell. Find it's neighbours in the direction of the corner and assign //create edges for sign changes. //in order to find sign changes we need to check each corners neighbours and see if they have a sign change //if we do, we actually creat the edge, and add the intersection to neighour cells QEF's //an edge is two corners. We can get corners by indicies. //we need to make sure we don't have duplicates though //we need to know the cells that share an edge, not verticies really //when we create the mesh we store neighbouring edges by faces. Can we presort the cells (the four cells used to construct the face) //so that they are always in some predetermined order, so when we add a new face we can check if it already exists before //adding it to the face list. This will prevent us from getting duplicates in the face list //shoudl we keep a shortlist of faces that have verts instead of searching through all the grid cells again? int vertcount = 0; Vector3 vertex = Vector3.zero; for (int x = 0; x < subdivisions; x++) { for (int y = 0; y < subdivisions; y++) { for (int z = 0; z < subdivisions; z++) { if (cells[x, y, z].hasVertex) { vertcount++; GridCell cell = cells[x, y, z]; cell.vertex = Vector3.zero; cell.qef.solve(cell.vertex, 1e-6f, 4, 1e-6f); Vector3 min = (cell.center - (Vector3.one * (cell.cellSize / 2f))); Vector3 max = (cell.center + (Vector3.one * (cell.cellSize / 2f))); if (cell.vertex.x <= min.x || cell.vertex.x >= max.x || cell.vertex.y <= min.y || cell.vertex.y >= max.y || cell.vertex.z <= min.z || cell.vertex.z >= max.z) { cell.vertex = cell.qef.getMassPoint(); } //cell.vertex = cell.qef.getMassPoint(); //Debug.Log("xyz:" + x + " : " + y + " : " + z); //Debug.Log("Vertex: " + cell.vertex.ToString()); //Debug.Log("Edges: " + cell.edgeCount); } } } } Debug.Log("Total Verts: " + vertcount); Debug.Log("Total Faces: " + faces.Count); //construct the mesh MeshRenderer mr = this.gameObject.GetComponent <MeshRenderer>(); MeshFilter mf = null; if (mr == null) { mr = this.gameObject.AddComponent <MeshRenderer>(); mf = this.gameObject.AddComponent <MeshFilter>(); } else { mf = this.gameObject.GetComponent <MeshFilter>(); } mf.mesh.Clear(); mr.sharedMaterial = Resources.Load("Default") as Material; Vector3[] vertArray = new Vector3[faces.Count * 4]; Vector3[] normArray = new Vector3[faces.Count * 4]; List <int> triList = new List <int>(); List <Vector3> verts = new List <Vector3>(); List <Vector3> norms = new List <Vector3>(); foreach (List <GridFace> c in faces.Values) { for (int i = 0; i < c.Count; i++) { GridFace f = c[i]; GridCell c0 = f.faces[0]; GridCell c1 = f.faces[1]; GridCell c2 = f.faces[2]; GridCell c3 = f.faces[3]; if (c0.vertexIndex == -1) { verts.Add(c0.vertex); c0.vertexIndex = verts.Count - 1; norms.Add((c0.normal / c0.edgeCount).normalized); } if (c1.vertexIndex == -1) { verts.Add(c1.vertex); norms.Add((c1.normal / c1.edgeCount).normalized); c1.vertexIndex = verts.Count - 1; } if (c2.vertexIndex == -1) { verts.Add(c2.vertex); norms.Add((c2.normal / c2.edgeCount).normalized); c2.vertexIndex = verts.Count - 1; } if (c3.vertexIndex == -1) { verts.Add(c3.vertex); norms.Add((c3.normal / c3.edgeCount).normalized); c3.vertexIndex = verts.Count - 1; } triList.Add(c0.vertexIndex); triList.Add(c1.vertexIndex); triList.Add(c3.vertexIndex); triList.Add(c3.vertexIndex); triList.Add(c2.vertexIndex); triList.Add(c0.vertexIndex); //every time we add a vertex to the list, we assign it's vertex index, //we don't want to add the vertex if it's vertex index is NOT -1, it means we've already assigned it. //will we get winding order issues? YEP. This entire system won't work with the way we've tried to smartly sort stuff because //sorting messes with the winding order! //can we do some magic with the winding order here? order the triangles differently based on...something? } } mf.mesh.vertices = verts.ToArray(); mf.mesh.triangles = triList.ToArray(); mf.mesh.normals = norms.ToArray(); }