public void AddQEF(Vector3 position, Vector3 normal) { if (qef == null) { qef = new QefSolver(); } qef.add(position, normal); this.normal += normal; edgeCount++; hasVertex = true; }
//semplifica l'octree public OctreeNode SimplifyOctree(OctreeNode node, float threshold) { if (node == null) { return(null); } if (node.type != NodeType.INTERNAL) { // can't simplify! return(node); } QefSolver qef = new QefSolver(); int[] signs = new int[8] { -1, -1, -1, -1, -1, -1, -1, -1, }; int midsign = -1; int edgeCount = 0; bool isCollapsible = true; for (int i = 0; i < 8; i++) { node.children[i] = SimplifyOctree(node.children[i], threshold); if (node.children[i] != null) { OctreeNode child = node.children[i]; if (child.type == NodeType.INTERNAL) { isCollapsible = false; } else { qef.Add(child.nodeInfo.qef); midsign = (child.nodeInfo.corners >> (7 - i)) & 1; signs[i] = (child.nodeInfo.corners >> i) & 1; edgeCount++; } } } if (!isCollapsible) { // at least one child is an internal node, can't collapse return(node); } Vector3 qefPosition = new Vector3(); float error = qef.Solve(qefPosition, QEF_ERROR, QEF_SWEEPS, QEF_ERROR); Vector3 position = new Vector3(qefPosition.x, qefPosition.y, qefPosition.z); // at this point the masspoint will actually be a sum, so divide to make it the average if (error > threshold) { // this collapse breaches the threshold return(node); } if (position.x < node.min.x || position.x > (node.min.x + node.size) || position.y < node.min.y || position.y > (node.min.y + node.size) || position.z < node.min.z || position.z > (node.min.z + node.size)) { position = qef.GetMassPoint(); } // change the node from an internal node to a 'pseudo leaf' node NodeInfo info = new NodeInfo(); for (int i = 0; i < 8; i++) { if (signs[i] == -1) { // Undetermined, use centre sign instead info.corners |= (midsign << i); } else { info.corners |= (signs[i] << i); } } info.avgNormal = new Vector3(0, 0, 0); for (int i = 0; i < 8; i++) { if (node.children[i] != null) { if (node.children[i].type == NodeType.PSEUDO || node.children[i].type == NodeType.LEAF) { info.avgNormal += node.children[i].nodeInfo.avgNormal; } } } info.avgNormal = info.avgNormal.normalized; info.position = position; info.qef = qef.GetData(); for (int i = 0; i < 8; i++) { DestroyOctree(node.children[i]); node.children[i] = null; } node.type = NodeType.PSEUDO; node.nodeInfo.Set(info); return(node); }
//costruisce il nodo terminale (foglia) calcolando le NodeInfo per la generazione della mesh public OctreeNode BuildLeaf(OctreeNode leaf, int size) { if (leaf == null || leaf.size != size) { return(null); } int corners = 0; for (int i = 0; i < 8; i++) { Vector3 cornerPos = leaf.min + (CHILD_MIN_OFFSETS[i] * size); float density = Density.DensityFunc(cornerPos); int material = density < 0.0f ? SOLID : AIR; corners |= (material << i); } if (corners == 0 || corners == 255) { leaf = null; return(null); } int edgeCount = 0; Vector3 averageNormal = new Vector3(); QefSolver qef = new QefSolver(); for (int i = 0; i < 12 && edgeCount < MAX_CROSSINGS; i++) { int c1 = edgevmap[i][0]; int c2 = edgevmap[i][1]; int m1 = (corners >> c1) & 1; int m2 = (corners >> c2) & 1; if ((m1 == AIR && m2 == AIR) || (m1 == SOLID && m2 == SOLID)) { continue; } Vector3 p1 = leaf.min + (CHILD_MIN_OFFSETS[c1] * size); Vector3 p2 = leaf.min + (CHILD_MIN_OFFSETS[c2] * size); Vector3 p = ApproximateZeroCrossingPosition(p1, p2, 8); Vector3 n = CalculateSurfaceNormal(p); qef.Add(p.x, p.y, p.z, n.x, n.y, n.z); averageNormal += n; edgeCount++; } Vector3 qefPosition = new Vector3(); qef.Solve(qefPosition, QEF_ERROR, QEF_SWEEPS, QEF_ERROR); NodeInfo info = new NodeInfo(); info.index = -1; info.corners = 0; info.position = qefPosition; info.qef = qef.GetData(); Vector3 min = leaf.min; Vector3 max = new Vector3(leaf.min.x + leaf.size, leaf.min.y + leaf.size, leaf.min.z + leaf.size); if (info.position.x < min.x || info.position.x > max.x || info.position.y < min.y || info.position.y > max.y || info.position.z < min.z || info.position.z > max.z) { info.position = qef.GetMassPoint(); } info.avgNormal = (averageNormal / (float)edgeCount).normalized; info.corners = corners; leaf.type = NodeType.LEAF; leaf.nodeInfo = info; return(leaf); }
private QefSolver(QefSolver rhs) { }
public static OctreeNode ConstructLeaf(OctreeNode leaf) { if (leaf == null || leaf.size != 1) { return(null); } int corners = 0; for (int i = 0; i < 8; i++) { Vector3 cornerPos = leaf.min + CHILD_MIN_OFFSETS[i]; float density = glm.Density_Func(cornerPos); int material = density < 0.0f ? MATERIAL_SOLID : MATERIAL_AIR; corners |= (material << i); } if (corners == 0 || corners == 255) { // voxel is full inside or outside the volume //delete leaf //setting as null isn't required by the GC in C#... but its in the original, so why not! leaf = null; return(null); } // otherwise the voxel contains the surface, so find the edge intersections const int MAX_CROSSINGS = 6; int edgeCount = 0; Vector3 averageNormal = Vector3.zero; QefSolver qef = new QefSolver(); for (int i = 0; i < 12 && edgeCount < MAX_CROSSINGS; i++) { int c1 = edgevmap[i][0]; int c2 = edgevmap[i][1]; int m1 = (corners >> c1) & 1; int m2 = (corners >> c2) & 1; if ((m1 == MATERIAL_AIR && m2 == MATERIAL_AIR) || (m1 == MATERIAL_SOLID && m2 == MATERIAL_SOLID)) { // no zero crossing on this edge continue; } Vector3 p1 = leaf.min + CHILD_MIN_OFFSETS[c1]; Vector3 p2 = leaf.min + CHILD_MIN_OFFSETS[c2]; Vector3 p = ApproximateZeroCrossingPosition(p1, p2); Vector3 n = CalculateSurfaceNormal(p); qef.add(p.x, p.y, p.z, n.x, n.y, n.z); averageNormal += n; edgeCount++; } Vector3 qefPosition = Vector3.zero; qef.solve(qefPosition, QEF_ERROR, QEF_SWEEPS, QEF_ERROR); OctreeDrawInfo drawInfo = new OctreeDrawInfo(); drawInfo.corners = 0; drawInfo.index = -1; drawInfo.position = new Vector3(qefPosition.x, qefPosition.y, qefPosition.z); drawInfo.qef = qef.getData(); Vector3 min = leaf.min; Vector3 max = new Vector3(leaf.min.x + leaf.size, leaf.min.y + leaf.size, leaf.min.z + leaf.size); if (drawInfo.position.x < min.x || drawInfo.position.x > max.x || drawInfo.position.y < min.y || drawInfo.position.y > max.y || drawInfo.position.z < min.z || drawInfo.position.z > max.z) { drawInfo.position = qef.getMassPoint(); } drawInfo.averageNormal = Vector3.Normalize(averageNormal / (float)edgeCount); drawInfo.corners = corners; leaf.Type = OctreeNodeType.Node_Leaf; leaf.drawInfo = drawInfo; return(leaf); }
public static OctreeNode SimplifyOctree(OctreeNode node, float threshold) { if (node == null) { return(null); } if (node.Type != OctreeNodeType.Node_Internal) { // can't simplify! return(node); } QefSolver qef = new QefSolver(); int[] signs = new int[8] { -1, -1, -1, -1, -1, -1, -1, -1 }; int midsign = -1; int edgeCount = 0; bool isCollapsible = true; for (int i = 0; i < 8; i++) { node.children[i] = SimplifyOctree(node.children[i], threshold); if (node.children[i] != null) { OctreeNode child = node.children[i]; if (child.Type == OctreeNodeType.Node_Internal) { isCollapsible = false; } else { qef.add(child.drawInfo.qef); midsign = (child.drawInfo.corners >> (7 - i)) & 1; signs[i] = (child.drawInfo.corners >> i) & 1; edgeCount++; } } } if (!isCollapsible) { // at least one child is an internal node, can't collapse return(node); } Vector3 qefPosition = Vector3.zero; qef.solve(qefPosition, QEF_ERROR, QEF_SWEEPS, QEF_ERROR); float error = qef.getError(); // convert to glm vec3 for ease of use Vector3 position = new Vector3(qefPosition.x, qefPosition.y, qefPosition.z); // at this point the masspoint will actually be a sum, so divide to make it the average if (error > threshold) { // this collapse breaches the threshold return(node); } if (position.x < node.min.x || position.x > (node.min.x + node.size) || position.y < node.min.y || position.y > (node.min.y + node.size) || position.z < node.min.z || position.z > (node.min.z + node.size)) { position = qef.getMassPoint(); } // change the node from an internal node to a 'psuedo leaf' node OctreeDrawInfo drawInfo = new OctreeDrawInfo(); drawInfo.corners = 0; drawInfo.index = -1; for (int i = 0; i < 8; i++) { if (signs[i] == -1) { // Undetermined, use centre sign instead drawInfo.corners |= (midsign << i); } else { drawInfo.corners |= (signs[i] << i); } } drawInfo.averageNormal = Vector3.zero; for (int i = 0; i < 8; i++) { if (node.children[i] != null) { OctreeNode child = node.children[i]; if (child.Type == OctreeNodeType.Node_Psuedo || child.Type == OctreeNodeType.Node_Leaf) { drawInfo.averageNormal += child.drawInfo.averageNormal; } } } drawInfo.averageNormal = drawInfo.averageNormal.normalized; drawInfo.position = position; drawInfo.qef = qef.getData(); for (int i = 0; i < 8; i++) { DestroyOctree(node.children[i]); node.children[i] = null; } node.Type = OctreeNodeType.Node_Psuedo; node.drawInfo = drawInfo; return(node); }
public static OctreeNode SimplifyOctree(OctreeNode node, double threshold) { if (node == null) { return(null); } if (node.Type != OctreeNodeType.Node_Internal) { // can't simplify! return(node); } var qef = new QefSolver(); int[] signs = new int[8] { -1, -1, -1, -1, -1, -1, -1, -1 }; int midsign = -1; int edgeCount = 0; bool isCollapsible = true; for (int i = 0; i < 8; i++) { node.Children[i] = SimplifyOctree(node.Children[i], threshold); if (node.Children[i] != null) { OctreeNode child = node.Children[i]; if (child.Type == OctreeNodeType.Node_Internal) { isCollapsible = false; } else { qef.Add(child.drawInfo.qefData); midsign = (child.drawInfo.corners >> (7 - i)) & 1; signs[i] = (child.drawInfo.corners >> i) & 1; edgeCount++; } } } if (!isCollapsible) { // at least one child is an internal node, can't collapse return(node); } Vector3 position = qef.Solve(QEF_ERROR, QEF_SWEEPS, QEF_ERROR); double error = qef.GetError(); // at this point the masspoint will actually be a sum, so divide to make it the average if (error > threshold) { // this collapse breaches the threshold return(node); } if (position.X < node.Min.X || position.X > (node.Min.X + node.Size.X) || position.Y < node.Min.Y || position.Y > (node.Min.Y + node.Size.Y) || position.Z < node.Min.Z || position.Z > (node.Min.Z + node.Size.Z)) { position = qef.GetMassPoint(); } // change the node from an internal node to a 'psuedo leaf' node var drawInfo = new OctreeDrawInfo(); drawInfo.corners = 0; drawInfo.index = -1; for (int i = 0; i < 8; i++) { if (signs[i] == -1) { // Undetermined, use center sign instead drawInfo.corners |= (midsign << i); } else { drawInfo.corners |= (signs[i] << i); } } drawInfo.averageNormal = Vector3.Zero; for (int i = 0; i < 8; i++) { if (node.Children[i] != null) { OctreeNode child = node.Children[i]; if (child.Type == OctreeNodeType.Node_Psuedo || child.Type == OctreeNodeType.Node_Leaf) { drawInfo.averageNormal += child.drawInfo.averageNormal; } } } drawInfo.averageNormal = drawInfo.averageNormal.GetNormal(); drawInfo.position = position; drawInfo.qefData = qef.QefData; for (int i = 0; i < 8; i++) { DestroyOctree(node.Children[i]); node.Children[i] = null; } node.Type = OctreeNodeType.Node_Psuedo; node.drawInfo = drawInfo; return(node); }
public static OctreeNode ConstructLeaf(Func <Vector3, double> f, OctreeNode leaf) { if (leaf == null || leaf.Level != 1) { return(null); } int corners = 0; for (int i = 0; i < 8; i++) { Vector3 cornerPos = leaf.Min + CHILD_MIN_OFFSETS[i] * leaf.Size; double density = f(cornerPos); int material = density < 0.0f ? MATERIAL_SOLID : MATERIAL_AIR; corners |= (material << i); } if (corners == 0 || corners == 255) { // voxel is full inside or outside the volume //delete leaf return(null); } // otherwise the voxel contains the surface, so find the edge intersections const int MAX_CROSSINGS = 6; int edgeCount = 0; Vector3 averageNormal = Vector3.Zero; var qefSolver = new QefSolver(); var debugError = .1; bool is5Ish = false; for (int i = 0; i < 12 && edgeCount < MAX_CROSSINGS; i++) { int c1 = edgevmap[i][0]; int c2 = edgevmap[i][1]; int m1 = (corners >> c1) & 1; int m2 = (corners >> c2) & 1; if ((m1 == MATERIAL_AIR && m2 == MATERIAL_AIR) || (m1 == MATERIAL_SOLID && m2 == MATERIAL_SOLID)) { // no zero crossing on this edge continue; } Vector3 p1 = leaf.Min + CHILD_MIN_OFFSETS[c1] * leaf.Size; Vector3 p2 = leaf.Min + CHILD_MIN_OFFSETS[c2] * leaf.Size; Vector3 position = ApproximateZeroCrossingPosition(f, p1, p2); is5Ish = Math.Abs(Math.Abs(position[0]) - 5) < debugError; is5Ish |= Math.Abs(Math.Abs(position[1]) - 5) < debugError; is5Ish |= Math.Abs(Math.Abs(position[2]) - 5) < debugError; if (!is5Ish) { int a = 0; } Vector3 normal = CalculateSurfaceNormal(f, position); qefSolver.Add(position, normal); averageNormal += normal; edgeCount++; } Vector3 qefPosition = qefSolver.Solve(QEF_ERROR, QEF_SWEEPS, QEF_ERROR); is5Ish = Math.Abs(Math.Abs(qefPosition[0]) - 5) < debugError; is5Ish |= Math.Abs(Math.Abs(qefPosition[1]) - 5) < debugError; is5Ish |= Math.Abs(Math.Abs(qefPosition[2]) - 5) < debugError; if (!is5Ish) { int a = 0; } var drawInfo = new OctreeDrawInfo(); drawInfo.corners = 0; drawInfo.index = -1; drawInfo.position = new Vector3(qefPosition.X, qefPosition.Y, qefPosition.Z); drawInfo.qefData = qefSolver.QefData; Vector3 min = leaf.Min; var max = min + leaf.Size; if (drawInfo.position.X < min.X || drawInfo.position.X > max.X || drawInfo.position.Y < min.Y || drawInfo.position.Y > max.Y || drawInfo.position.Z < min.Z || drawInfo.position.Z > max.Z) { drawInfo.position = qefSolver.GetMassPoint(); } drawInfo.averageNormal = Vector3.Normalize(averageNormal); drawInfo.corners = corners; leaf.Type = OctreeNodeType.Node_Leaf; leaf.drawInfo = drawInfo; return(leaf); }
public GridCell(Vector3 center, float gridScale = 1f) { QefSolver qef = new QefSolver(); //check each corner against the density function //and generate SOMETHING at that position to see if our density function is right //we can cache the results from each corner, as we need to check every one anyways //is there a smarter way //we can have up to 4 sign changes per cell.. // 0 ---- 1 // | | // 1------0 <---- opposite for the top face = 4 total //get corner positions //take the center (int coords), convert them to world positions, then +/- on every axis this.gridScale = gridScale; worldPos = center * gridScale; //need to do corners in the same order every time //assign the corners to their density value at that corner position for (int i = 0; i < 8; i++) { float v = DualContouring1.density(worldPos + DualContouring1.cornerOffsets[i] * 0.5f * gridScale); //thresholding here to turn the density into an ID corners[i] = v >= 0 ? 1 : 0; } //check if any edges have sign changes //I guess only store the edges that are important, like the ones that have a sign change. //we need to check every corner against it's neighbour cells, so we need to know which ones are it's neighbours. //Each corner has three neighbours, for (int i = 0; i < 4; i++) //but we only want to check half of them, otherwise we have doubles? { for (int j = 0; j < 3; j++) { int neighbour = cornerNeighbours[i, j]; if (corners[i] != corners[neighbour]) //if this corner has a different density value than any of it's neighbours //we need to be able to find neighbours based on edges... //not sure how //make a naive implimentation first { GridEdge e = new GridEdge(i, neighbour, center); edges.Add(e); DualContouring1.edges.Add(e); Vector3 edgeCornerA = worldPos + DualContouring1.cornerOffsets[e.corners[0]] * 0.5f * gridScale; Vector3 edgeCornerB = worldPos + DualContouring1.cornerOffsets[e.corners[1]] * 0.5f * gridScale; //need these for meshing to find shared edges I guess e.cornersOffset[0] = DualContouring1.cornerOffsets[e.corners[0]]; e.cornersOffset[1] = DualContouring1.cornerOffsets[e.corners[1]]; //computing the intersection position and normal of this edge against the density function e.position = DualContouring1.ApproximateEdgeIntersection(edgeCornerA, edgeCornerB, DualContouring1.density); e.normal = DualContouring1.CalculateSurfaceNormal(e.position, DualContouring1.density); //from here we have to get the actual position of the vertex for this cell //we do this using the QefSolver. For every edge intersection add the position and normal qef.add(e.position, e.normal); //add the normal so we can grab it later (summed, so we have to grab normal/edges.Count normal += e.normal; Debug.Log("1"); } } } if (edges.Count != 0) { vertex = Vector3.zero; qef.solve(vertex, QEF_ERROR, QEF_SWEEPS, QEF_ERROR); Vector3 min = center + DualContouring1.cornerOffsets[0] * 0.5f * gridScale; Vector3 max = center + DualContouring1.cornerOffsets[6] * 0.5f * gridScale; if (vertex.x < min.x || vertex.x > max.x || vertex.y < min.y || vertex.y > max.y || vertex.z < min.z || vertex.z > max.z) { vertex = qef.getMassPoint(); } DualContouring1.verticies.Add(new MeshVertex(vertex, (normal / edges.Count).normalized)); vertexIndex = DualContouring1.verticies.Count - 1; if (vertex == Vector3.zero) { Debug.Log("Zero"); } Debug.Log("2"); hasVertex = true; } }