public static bool GenerateBoxSubMesh(CSGBrushSubMesh subMesh, UnityEngine.Vector3 min, UnityEngine.Vector3 max, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { if (!BoundsExtensions.IsValid(min, max)) { return(false); } if (surfaceAssets.Length != 6 || surfaceDescriptions.Length != 6) { return(false); } if (min.x > max.x) { float x = min.x; min.x = max.x; max.x = x; } if (min.y > max.y) { float y = min.y; min.y = max.y; max.y = y; } if (min.z > max.z) { float z = min.z; min.z = max.z; max.z = z; } subMesh.Polygons = CreateBoxAssetPolygons(surfaceAssets, surfaceDescriptions); subMesh.HalfEdges = boxHalfEdges.ToArray(); subMesh.Vertices = BrushMeshFactory.CreateBoxVertices(min, max); return(true); }
public static bool GenerateSphereSubMesh(CSGBrushSubMesh subMesh, CSGSphereDefinition definition) { definition.Validate(); var transform = Matrix4x4.TRS(Vector3.zero, Quaternion.AngleAxis(definition.rotation, Vector3.up), Vector3.one); return(GenerateSphereSubMesh(subMesh, definition.diameterXYZ, definition.offsetY, definition.generateFromCenter, transform, definition.horizontalSegments, definition.verticalSegments, definition.surfaceAssets, definition.surfaceDescriptions)); }
public static bool GenerateSphereSubMesh(CSGBrushSubMesh subMesh, Vector3 diameterXYZ, float offsetY, bool generateFromCenter, Matrix4x4 transform, int horzSegments, int vertSegments, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { if (diameterXYZ.x == 0 || diameterXYZ.y == 0 || diameterXYZ.z == 0) { subMesh.Clear(); return(false); } var brushMesh = BrushMeshFactory.CreateSphere(diameterXYZ, offsetY, generateFromCenter, horzSegments, vertSegments); subMesh.HalfEdges = brushMesh.halfEdges; subMesh.Vertices = brushMesh.vertices; subMesh.Polygons = new CSGBrushSubMesh.Polygon[brushMesh.polygons.Length]; for (int i = 0; i < brushMesh.polygons.Length; i++) { subMesh.Polygons[i] = new CSGBrushSubMesh.Polygon { surfaceID = i, edgeCount = brushMesh.polygons[i].edgeCount, firstEdge = brushMesh.polygons[i].firstEdge, surfaceAsset = i < surfaceAssets.Length ? surfaceAssets[i] : surfaceAssets[0], description = i < surfaceDescriptions.Length ? surfaceDescriptions[i] : surfaceDescriptions[0], }; } return(true); }
public static bool GenerateConicalFrustumAsset(CSGBrushMeshAsset brushMeshAsset, CSGCircleDefinition bottom, CSGCircleDefinition top, float rotation, int segments, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { if (segments < 3 || (top.height - bottom.height) == 0 || (bottom.diameterX == 0 && top.diameterX == 0) || (bottom.diameterZ == 0 && top.diameterZ == 0)) { brushMeshAsset.Clear(); return(false); } if (surfaceAssets.Length != 3 || surfaceDescriptions.Length != segments + 2) { brushMeshAsset.Clear(); return(false); } var subMesh = new CSGBrushSubMesh(); if (!GenerateConicalFrustumSubMesh(subMesh, bottom, top, rotation, segments, surfaceAssets, surfaceDescriptions)) { brushMeshAsset.Clear(); return(false); } brushMeshAsset.SubMeshes = new CSGBrushSubMesh[] { subMesh }; brushMeshAsset.CalculatePlanes(); brushMeshAsset.SetDirty(); return(true); }
public static bool GenerateCylinderSubMesh(CSGBrushSubMesh subMesh, CSGCircleDefinition bottom, float topHeight, float rotation, int sides, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { CSGCircleDefinition top; top.diameterX = bottom.diameterX; top.diameterZ = bottom.diameterZ; top.height = topHeight; return(GenerateConicalFrustumSubMesh(subMesh, bottom, top, rotation, sides, surfaceAssets, surfaceDescriptions)); }
public static bool GenerateConicalFrustumSubMesh(CSGBrushSubMesh subMesh, CSGCircleDefinition bottom, CSGCircleDefinition top, float rotation, int segments, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { if (segments < 3 || (top.height - bottom.height) == 0 || (bottom.diameterX == 0 && top.diameterX == 0) || (bottom.diameterZ == 0 && top.diameterZ == 0)) { subMesh.Clear(); return(false); } if (surfaceAssets.Length < 3 || surfaceDescriptions.Length < segments + 2) { subMesh.Clear(); return(false); } // TODO: handle situation where diameterX & diameterZ are 0 (only create one vertex) if (top.diameterX == 0) { var vertices = new Vector3[segments + 1]; GetConeFrustumVertices(bottom, top.height, rotation, segments, ref vertices, inverse: false); // TODO: the polygon/half-edge part would be the same for any extruded shape and should be re-used CreateConeSubMesh(subMesh, segments, null, vertices, surfaceAssets, surfaceDescriptions); } else if (bottom.diameterX == 0) { var vertices = new Vector3[segments + 1]; GetConeFrustumVertices(top, bottom.height, rotation, segments, ref vertices, inverse: true); // TODO: the polygon/half-edge part would be the same for any extruded shape and should be re-used CreateConeSubMesh(subMesh, segments, null, vertices, surfaceAssets, surfaceDescriptions); } else { if (top.height > bottom.height) { var temp = top; top = bottom; bottom = temp; } var vertices = new Vector3[segments * 2]; GetConicalFrustumVertices(bottom, top, rotation, segments, ref vertices); // TODO: the polygon/half-edge part would be the same for any extruded shape and should be re-used CreateExtrudedSubMesh(subMesh, segments, null, 0, 1, vertices, surfaceAssets, surfaceDescriptions); } return(true); }
public static bool GenerateSphereAsset(CSGBrushMeshAsset brushMeshAsset, CSGSphereDefinition definition) { var subMesh = new CSGBrushSubMesh(); if (!GenerateSphereSubMesh(subMesh, definition)) { brushMeshAsset.Clear(); return(false); } brushMeshAsset.SubMeshes = new[] { subMesh }; brushMeshAsset.CalculatePlanes(); brushMeshAsset.SetDirty(); return(true); }
public static bool GenerateLinearStairsAsset(CSGBrushMeshAsset brushMeshAsset, CSGLinearStairsDefinition definition) { definition.Validate(); int subMeshCount = GetLinearStairsSubMeshCount(definition, definition.leftSide, definition.rightSide); if (subMeshCount == 0) { brushMeshAsset.Clear(); return(false); } CSGBrushSubMesh[] subMeshes; if (brushMeshAsset.SubMeshCount != subMeshCount) { subMeshes = new CSGBrushSubMesh[subMeshCount]; for (int i = 0; i < subMeshCount; i++) { subMeshes[i] = new CSGBrushSubMesh(); } } else { subMeshes = brushMeshAsset.SubMeshes; } if (!GenerateLinearStairsSubMeshes(subMeshes, definition, definition.leftSide, definition.rightSide, 0)) { brushMeshAsset.Clear(); return(false); } brushMeshAsset.SubMeshes = subMeshes; brushMeshAsset.CalculatePlanes(); brushMeshAsset.SetDirty(); return(true); }
public static bool GenerateHemisphereSubMesh(CSGBrushSubMesh subMesh, Vector3 diameterXYZ, Matrix4x4 transform, int horzSegments, int vertSegments, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { if (diameterXYZ.x == 0 || diameterXYZ.y == 0 || diameterXYZ.z == 0) { subMesh.Clear(); return(false); } var bottomCap = true; var topCap = false; var extraVertices = ((!bottomCap) ? 1 : 0) + ((!topCap) ? 1 : 0); var rings = (vertSegments) + (topCap ? 1 : 0); var vertexCount = (horzSegments * rings) + extraVertices; var topVertex = 0; var bottomVertex = (!topCap) ? 1 : 0; var radius = new Vector3(diameterXYZ.x * 0.5f, diameterXYZ.y, diameterXYZ.z * 0.5f); var heightY = radius.y; float topY, bottomY; if (heightY < 0) { topY = 0; bottomY = heightY; } else { topY = heightY; bottomY = 0; } var vertices = new Vector3[vertexCount]; if (!topCap) { vertices[topVertex] = transform.MultiplyPoint(Vector3.up * topY); // top } if (!bottomCap) { vertices[bottomVertex] = transform.MultiplyPoint(Vector3.up * bottomY); // bottom } var degreePerSegment = (360.0f / horzSegments) * Mathf.Deg2Rad; var angleOffset = ((horzSegments & 1) == 1) ? 0.0f : 0.5f * degreePerSegment; var vertexIndex = extraVertices; if (heightY < 0) { for (int h = horzSegments - 1; h >= 0; h--, vertexIndex++) { var hRad = (h * degreePerSegment) + angleOffset; vertices[vertexIndex] = transform.MultiplyPoint(new Vector3(Mathf.Cos(hRad) * radius.x, 0.0f, Mathf.Sin(hRad) * radius.z)); } } else { for (int h = 0; h < horzSegments; h++, vertexIndex++) { var hRad = (h * degreePerSegment) + angleOffset; vertices[vertexIndex] = transform.MultiplyPoint(new Vector3(Mathf.Cos(hRad) * radius.x, 0.0f, Mathf.Sin(hRad) * radius.z)); } } for (int v = 1; v < rings; v++) { var segmentFactor = ((v - (rings / 2.0f)) / rings) + 0.5f; // [0.0f ... 1.0f] var segmentDegree = (segmentFactor * 90); // [0 .. 90] var segmentHeight = Mathf.Sin(segmentDegree * Mathf.Deg2Rad) * heightY; var segmentRadius = Mathf.Cos(segmentDegree * Mathf.Deg2Rad); // [0 .. 0.707 .. 1 .. 0.707 .. 0] for (int h = 0; h < horzSegments; h++, vertexIndex++) { vertices[vertexIndex].x = vertices[h + extraVertices].x * segmentRadius; vertices[vertexIndex].y = segmentHeight; vertices[vertexIndex].z = vertices[h + extraVertices].z * segmentRadius; } } return(GenerateSegmentedSubMesh(subMesh, horzSegments, vertSegments, vertices, bottomCap, topCap, bottomVertex, topVertex, surfaceAssets, surfaceDescriptions)); }
public static bool GenerateRevolvedShapeAsset(CSGBrushMeshAsset brushMeshAsset, CSGRevolvedShapeDefinition definition) { definition.Validate(); var surfaces = definition.surfaceAssets; var descriptions = definition.surfaceDescriptions; var shapeVertices = new List <Vector2>(); var shapeSegmentIndices = new List <int>(); GetPathVertices(definition.shape, definition.curveSegments, shapeVertices, shapeSegmentIndices); Vector2[][] polygonVerticesArray; int[][] polygonIndicesArray; if (!Decomposition.ConvexPartition(shapeVertices, shapeSegmentIndices, out polygonVerticesArray, out polygonIndicesArray)) { brushMeshAsset.Clear(); return(false); } // TODO: splitting it before we do the composition would be better var polygonVerticesList = polygonVerticesArray.ToList(); for (int i = polygonVerticesList.Count - 1; i >= 0; i--) { SplitPolygon(polygonVerticesList, i); } var subMeshes = new List <CSGBrushSubMesh>(); var horzSegments = definition.revolveSegments; //horizontalSegments; var horzDegreePerSegment = definition.totalAngle / horzSegments; // TODO: make this work when intersecting rotation axis // 1. split polygons along rotation axis // 2. if edge lies on rotation axis, make sure we don't create infinitely thin quad // collapse this quad, or prevent this from happening // TODO: share this code with torus generator for (int p = 0; p < polygonVerticesList.Count; p++) { var polygonVertices = polygonVerticesList[p]; // var segmentIndices = polygonIndicesArray[p]; var shapeSegments = polygonVertices.Length; var vertSegments = polygonVertices.Length; var descriptionIndex = new int[2 + vertSegments]; descriptionIndex[0] = 0; descriptionIndex[1] = 1; for (int v = 0; v < vertSegments; v++) { descriptionIndex[v + 2] = 2; } var horzOffset = definition.startAngle; for (int h = 1, pr = 0; h < horzSegments + 1; pr = h, h++) { var hDegree0 = (pr * horzDegreePerSegment) + horzOffset; var hDegree1 = (h * horzDegreePerSegment) + horzOffset; var rotation0 = Quaternion.AngleAxis(hDegree0, Vector3.forward); var rotation1 = Quaternion.AngleAxis(hDegree1, Vector3.forward); var subMeshVertices = new Vector3[vertSegments * 2]; for (int v = 0; v < vertSegments; v++) { subMeshVertices[v + vertSegments] = rotation0 * new Vector3(polygonVertices[v].x, 0, polygonVertices[v].y); subMeshVertices[v] = rotation1 * new Vector3(polygonVertices[v].x, 0, polygonVertices[v].y); } var subMesh = new CSGBrushSubMesh(); if (!CreateExtrudedSubMesh(subMesh, vertSegments, descriptionIndex, descriptionIndex, 0, 1, subMeshVertices, surfaces, descriptions)) { continue; } if (!subMesh.Validate()) { brushMeshAsset.Clear(); return(false); } subMeshes.Add(subMesh); } } brushMeshAsset.SubMeshes = subMeshes.ToArray(); brushMeshAsset.CalculatePlanes(); brushMeshAsset.SetDirty(); return(true); }
public static bool GeneratePathedStairsAsset(CSGBrushMeshAsset brushMeshAsset, CSGPathedStairsDefinition definition) { definition.Validate(); var shapeVertices = new List <Vector2>(); var shapeSegmentIndices = new List <int>(); GetPathVertices(definition.shape, definition.curveSegments, shapeVertices, shapeSegmentIndices); var totalSubMeshCount = 0; for (int i = 0; i < shapeVertices.Count; i++) { if (i == 0 && !definition.shape.closed) { continue; } var leftSide = (!definition.shape.closed && i == 1) ? definition.stairs.leftSide : StairsSideType.None; var rightSide = (!definition.shape.closed && i == shapeVertices.Count - 1) ? definition.stairs.rightSide : StairsSideType.None; totalSubMeshCount += GetLinearStairsSubMeshCount(definition.stairs, leftSide, rightSide); } if (totalSubMeshCount == 0) { brushMeshAsset.Clear(); return(false); } // var stairDirections = definition.shape.closed ? shapeVertices.Count : (shapeVertices.Count - 1); // TODO: use list instead? CSGBrushSubMesh[] subMeshes; if (brushMeshAsset.SubMeshCount != totalSubMeshCount) { subMeshes = new CSGBrushSubMesh[totalSubMeshCount]; for (int i = 0; i < totalSubMeshCount; i++) { subMeshes[i] = new CSGBrushSubMesh(); } } else { subMeshes = brushMeshAsset.SubMeshes; } var depth = definition.stairs.depth; var height = definition.stairs.height; var halfDepth = depth * 0.5f; var halfHeight = height * 0.5f; int subMeshIndex = 0; for (int vi0 = shapeVertices.Count - 3, vi1 = shapeVertices.Count - 2, vi2 = shapeVertices.Count - 1, vi3 = 0; vi3 < shapeVertices.Count; vi0 = vi1, vi1 = vi2, vi2 = vi3, vi3++) { if (vi2 == 0 && !definition.shape.closed) { continue; } // TODO: optimize this, we're probably redoing a lot of stuff for every iteration var v0 = shapeVertices[vi0]; var v1 = shapeVertices[vi1]; var v2 = shapeVertices[vi2]; var v3 = shapeVertices[vi3]; var m0 = (v0 + v1) * 0.5f; var m1 = (v1 + v2) * 0.5f; var m2 = (v2 + v3) * 0.5f; var d0 = (v1 - v0); var d1 = (v2 - v1); var d2 = (v3 - v2); var maxWidth0 = d0.magnitude; var maxWidth1 = d1.magnitude; var maxWidth2 = d2.magnitude; var halfWidth1 = d1 * 0.5f; d0 /= maxWidth0; d1 /= maxWidth1; d2 /= maxWidth2; var depthVector = new Vector3(d1.y, 0, -d1.x); var lineCenter = new Vector3(m1.x, halfHeight, m1.y) - (depthVector * halfDepth); var depthVector0 = new Vector2(d0.y, -d0.x) * depth; var depthVector1 = new Vector2(d1.y, -d1.x) * depth; var depthVector2 = new Vector2(d2.y, -d2.x) * depth; m0 -= depthVector0; m1 -= depthVector1; m2 -= depthVector2; Vector2 output; var leftShear = Intersect(m1, d1, m0, d0, out output) ? Vector2.Dot(d1, (output - (m1 - halfWidth1))) : 0; var rightShear = Intersect(m1, d1, m2, d2, out output) ? -Vector2.Dot(d1, (output - (m1 + halfWidth1))) : 0; var transform = Matrix4x4.TRS(lineCenter, // move to center of line Quaternion.LookRotation(depthVector, Vector3.up), // rotate to align with line Vector3.one); // set the width to the width of the line definition.stairs.width = maxWidth1; definition.stairs.nosingWidth = 0; var leftSide = (!definition.shape.closed && vi2 == 1) ? definition.stairs.leftSide : StairsSideType.None; var rightSide = (!definition.shape.closed && vi2 == shapeVertices.Count - 1) ? definition.stairs.rightSide : StairsSideType.None; var subMeshCount = GetLinearStairsSubMeshCount(definition.stairs, leftSide, rightSide); if (subMeshCount == 0) { continue; } if (!GenerateLinearStairsSubMeshes(subMeshes, definition.stairs, leftSide, rightSide, subMeshIndex)) { brushMeshAsset.Clear(); return(false); } var halfWidth = maxWidth1 * 0.5f; for (int m = 0; m < subMeshCount; m++) { var vertices = subMeshes[subMeshIndex + m].Vertices; for (int v = 0; v < vertices.Length; v++) { // TODO: is it possible to put all of this in a single matrix? // lerp the stairs to go from less wide to wider depending on the depth of the vertex var depthFactor = 1.0f - ((vertices[v].z / definition.stairs.depth) + 0.5f); var wideFactor = (vertices[v].x / halfWidth) + 0.5f; var scale = (vertices[v].x / halfWidth); // lerp the stairs width depending on if it's on the left or right side of the stairs vertices[v].x = Mathf.Lerp(scale * (halfWidth - (rightShear * depthFactor)), scale * (halfWidth - (leftShear * depthFactor)), wideFactor); vertices[v] = transform.MultiplyPoint(vertices[v]); } } subMeshIndex += subMeshCount; } brushMeshAsset.SubMeshes = subMeshes; brushMeshAsset.CalculatePlanes(); brushMeshAsset.SetDirty(); return(false); }
static void CreateConeSubMesh(CSGBrushSubMesh subMesh, int segments, int[] segmentDescriptionIndices, int[] segmentAssetIndices, Vector3[] vertices, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { var surfaceDescription0 = surfaceDescriptions[0]; var surfaceAsset0 = surfaceAssets[0]; var polygons = new CSGBrushSubMesh.Polygon[segments + 1]; polygons[0] = new CSGBrushSubMesh.Polygon { surfaceID = 0, firstEdge = 0, edgeCount = segments, description = surfaceDescription0, surfaceAsset = surfaceAsset0 }; for (int s = 0, surfaceID = 1; s < segments; s++) { var descriptionIndex = (segmentDescriptionIndices == null) ? s + 2 : (segmentDescriptionIndices[s + 2]); var assetIndex = (segmentAssetIndices == null) ? 2 : (segmentAssetIndices [s + 2]); polygons[surfaceID] = new CSGBrushSubMesh.Polygon { surfaceID = surfaceID, firstEdge = segments + (s * 3), edgeCount = 3, description = surfaceDescriptions[descriptionIndex], surfaceAsset = surfaceAssets[assetIndex] }; polygons[surfaceID].description.smoothingGroup = (uint)0; surfaceID++; } var halfEdges = new BrushMesh.HalfEdge[4 * segments]; for (int n = 0, e = segments - 1; n < segments; e = n, n++) { var p = (n + 1) % segments; // TODO: optimize away // v0 // * // ^ /^ \ // / / \ \ // / / e2 \ \ // / / \ \ // t2 / /e3 q2 e2\ \ t3 // / / \ \ // / v e1 \ v // ------> -----------> -----> // v2 * * v1 // <----- <---------- <----- // e0 var v0 = 0; var v1 = n + 1; var v2 = e + 1; var e0 = n; var e1 = ((n * 3) + segments) + 0; var e2 = ((n * 3) + segments) + 1; var e3 = ((n * 3) + segments) + 2; var t3 = ((e * 3) + segments) + 2; var t2 = ((p * 3) + segments) + 1; // bottom polygon halfEdges[e0] = new BrushMesh.HalfEdge { vertexIndex = v1, twinIndex = e1 }; // triangle halfEdges[e1] = new BrushMesh.HalfEdge { vertexIndex = v2, twinIndex = e0 }; halfEdges[e2] = new BrushMesh.HalfEdge { vertexIndex = v0, twinIndex = t3 }; halfEdges[e3] = new BrushMesh.HalfEdge { vertexIndex = v1, twinIndex = t2 }; } subMesh.Polygons = polygons; subMesh.HalfEdges = halfEdges; subMesh.Vertices = vertices; subMesh.CreateOrUpdateBrushMesh(); }
static void CreateConeSubMesh(CSGBrushSubMesh subMesh, int segments, int[] segmentDescriptionIndices, Vector3[] vertices, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { CreateConeSubMesh(subMesh, segments, segmentDescriptionIndices, null, vertices, surfaceAssets, surfaceDescriptions); }
public static bool GenerateTorusAsset(CSGBrushMeshAsset brushMeshAsset, CSGTorusDefinition definition) { Vector3[] vertices = null; if (!GenerateTorusVertices(definition, ref vertices)) { brushMeshAsset.Clear(); return(false); } definition.Validate(); var surfaces = definition.surfaceAssets; var descriptions = definition.surfaceDescriptions; var tubeRadiusX = (definition.tubeWidth * 0.5f); var tubeRadiusY = (definition.tubeHeight * 0.5f); var torusRadius = (definition.outerDiameter * 0.5f) - tubeRadiusX; var horzSegments = definition.horizontalSegments; var vertSegments = definition.verticalSegments; var horzDegreePerSegment = (definition.totalAngle / horzSegments); var vertDegreePerSegment = (360.0f / vertSegments) * Mathf.Deg2Rad; var descriptionIndex = new int[2 + vertSegments]; descriptionIndex[0] = 0; descriptionIndex[1] = 1; var circleVertices = new Vector2[vertSegments]; var min = new Vector2(float.PositiveInfinity, float.PositiveInfinity); var max = new Vector2(float.NegativeInfinity, float.NegativeInfinity); var tubeAngleOffset = ((((vertSegments & 1) == 1) ? 0.0f : ((360.0f / vertSegments) * 0.5f)) + definition.tubeRotation) * Mathf.Deg2Rad; for (int v = 0; v < vertSegments; v++) { var vRad = tubeAngleOffset + (v * vertDegreePerSegment); circleVertices[v] = new Vector2((Mathf.Cos(vRad) * tubeRadiusX) - torusRadius, (Mathf.Sin(vRad) * tubeRadiusY)); min.x = Mathf.Min(min.x, circleVertices[v].x); min.y = Mathf.Min(min.y, circleVertices[v].y); max.x = Mathf.Max(max.x, circleVertices[v].x); max.y = Mathf.Max(max.y, circleVertices[v].y); descriptionIndex[v + 2] = 2; } if (definition.fitCircle) { var center = (max + min) * 0.5f; var size = (max - min) * 0.5f; size.x = tubeRadiusX / size.x; size.y = tubeRadiusY / size.y; for (int v = 0; v < vertSegments; v++) { circleVertices[v].x = (circleVertices[v].x - center.x) * size.x; circleVertices[v].y = (circleVertices[v].y - center.y) * size.y; circleVertices[v].x -= torusRadius; } } var subMeshes = new CSGBrushSubMesh[horzSegments]; var horzOffset = definition.startAngle; for (int h = 1, p = 0; h < horzSegments + 1; p = h, h++) { var hDegree0 = (p * horzDegreePerSegment) + horzOffset; var hDegree1 = (h * horzDegreePerSegment) + horzOffset; var rotation0 = Quaternion.AngleAxis(hDegree0, Vector3.up); var rotation1 = Quaternion.AngleAxis(hDegree1, Vector3.up); var subMeshVertices = new Vector3[vertSegments * 2]; for (int v = 0; v < vertSegments; v++) { subMeshVertices[v + vertSegments] = rotation0 * circleVertices[v]; subMeshVertices[v] = rotation1 * circleVertices[v]; } var subMesh = new CSGBrushSubMesh(); CreateExtrudedSubMesh(subMesh, vertSegments, descriptionIndex, descriptionIndex, 0, 1, subMeshVertices, surfaces, descriptions); if (!subMesh.Validate()) { brushMeshAsset.Clear(); return(false); } subMeshes[h - 1] = subMesh; } brushMeshAsset.SubMeshes = subMeshes; brushMeshAsset.CalculatePlanes(); brushMeshAsset.SetDirty(); return(true); }
static bool CreateExtrudedSubMesh(CSGBrushSubMesh subMesh, int segments, int[] segmentDescriptionIndices, int segmentTopIndex, int segmentBottomIndex, Vector3[] vertices, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { return(CreateExtrudedSubMesh(subMesh, segments, segmentDescriptionIndices, null, segmentTopIndex, segmentBottomIndex, vertices, surfaceAssets, surfaceDescriptions)); }
public static bool GenerateSpiralStairsAsset(CSGBrushMeshAsset brushMeshAsset, ref CSGSpiralStairsDefinition definition, CSGSurfaceAsset[] surfaceAssets, ref SurfaceDescription[] surfaceDescriptions) { if (surfaceAssets == null || surfaceDescriptions == null || surfaceAssets.Length != 6 || surfaceDescriptions.Length != 6) { brushMeshAsset.Clear(); return(false); } definition.Validate(); const float kEpsilon = 0.001f; var nosingDepth = definition.nosingDepth; var treadHeight = (nosingDepth < kEpsilon) ? 0 : definition.treadHeight; var haveTread = (treadHeight >= kEpsilon); var innerDiameter = definition.innerDiameter; var haveInnerCyl = (innerDiameter >= kEpsilon); var riserType = definition.riserType; var haveRiser = riserType != StairsRiserType.None; if (!haveRiser && !haveTread) { brushMeshAsset.Clear(); return(false); } var origin = definition.origin; var startAngle = definition.startAngle * Mathf.Deg2Rad; var anglePerStep = definition.AnglePerStep * Mathf.Deg2Rad; var nosingWidth = definition.nosingWidth; var outerDiameter = definition.outerDiameter; var p0 = new Vector2(Mathf.Sin(0), Mathf.Cos(0)); var p1 = new Vector2(Mathf.Sin(anglePerStep), Mathf.Cos(anglePerStep)); var pm = new Vector2(Mathf.Sin(anglePerStep * 0.5f), Mathf.Cos(anglePerStep * 0.5f)); var pn = (p0 + p1) * 0.5f; var stepOuterDiameter = outerDiameter + ((pm.magnitude - pn.magnitude) * (outerDiameter * 1.25f)); // TODO: figure out why we need the 1.25 magic number to fit the step in the outerDiameter? var stepOuterRadius = stepOuterDiameter * 0.5f; var stepInnerRadius = 0.0f; var stepHeight = definition.stepHeight; var height = definition.height; var stepCount = definition.StepCount; if (height < 0) { origin.y += height; height = -height; } // TODO: expose this to user var smoothSubDivisions = 3; var cylinderSubMeshCount = haveInnerCyl ? 2 : 1; var subMeshPerRiser = (riserType == StairsRiserType.None) ? 0 : (riserType == StairsRiserType.Smooth) ? (2 * smoothSubDivisions) : 1; var riserSubMeshCount = (stepCount * subMeshPerRiser) + ((riserType == StairsRiserType.None) ? 0 : cylinderSubMeshCount); var treadSubMeshCount = (haveTread ? stepCount + cylinderSubMeshCount : 0); var subMeshCount = (treadSubMeshCount + riserSubMeshCount); var treadStart = !haveRiser ? 0 : riserSubMeshCount; var innerSides = definition.innerSegments; var outerSides = definition.outerSegments; var riserDepth = definition.riserDepth; CSGBrushSubMesh[] subMeshes; if (brushMeshAsset.SubMeshCount != subMeshCount) { subMeshes = new CSGBrushSubMesh[subMeshCount]; for (int i = 0; i < subMeshCount; i++) { subMeshes[i] = new CSGBrushSubMesh(); } } else { subMeshes = brushMeshAsset.SubMeshes; } if (haveRiser) { if (riserType == StairsRiserType.ThinRiser) { var minY = origin.y; var maxY = origin.y + stepHeight - treadHeight; Vector2 o0, o1; float angle = startAngle; var c1 = Mathf.Sin(angle) * stepOuterRadius; var s1 = Mathf.Cos(angle) * stepOuterRadius; for (int i = 0; i < stepCount; i++) { var c0 = c1; var s0 = s1; angle += anglePerStep; c1 = Mathf.Sin(angle) * stepOuterRadius; s1 = Mathf.Cos(angle) * stepOuterRadius; o0 = new Vector2(origin.x + c0, origin.z + s0); o1 = new Vector2(origin.x, origin.z); var riserVector = (new Vector2((c0 - c1), (s0 - s1)).normalized) * riserDepth; var i0 = o0 - riserVector; var i1 = o1 - riserVector; var vertices = new[] { new Vector3(i0.x, maxY, i0.y), // 0 new Vector3(i1.x, maxY, i1.y), // 1 new Vector3(o1.x, maxY, o1.y), // 2 new Vector3(o0.x, maxY, o0.y), // 3 new Vector3(i0.x, minY, i0.y), // 4 new Vector3(i1.x, minY, i1.y), // 5 new Vector3(o1.x, minY, o1.y), // 6 new Vector3(o0.x, minY, o0.y), // 7 }; if (i == 0) { subMeshes[i].Polygons = CreateBoxAssetPolygons(surfaceAssets, surfaceDescriptions); minY -= treadHeight; } else { subMeshes[i].Polygons = subMeshes[0].Polygons.ToArray(); } subMeshes[i].HalfEdges = (anglePerStep > 0) ? invertedBoxHalfEdges.ToArray() : boxHalfEdges.ToArray(); subMeshes[i].Vertices = vertices; minY += stepHeight; maxY += stepHeight; } } else if (riserType == StairsRiserType.Smooth) { //var stepY = stepHeight; var minY = origin.y; var maxY = origin.y + stepHeight - treadHeight; var maxY2 = origin.y + (stepHeight * 2) - treadHeight; float angle = startAngle; var c1 = Mathf.Sin(angle); var s1 = Mathf.Cos(angle); angle += anglePerStep; var c2 = Mathf.Sin(angle); var s2 = Mathf.Cos(angle); for (int i = 0; i < riserSubMeshCount; i += subMeshPerRiser) { var c0 = c1; var s0 = s1; c1 = c2; s1 = s2; angle += anglePerStep; c2 = Mathf.Sin(angle); s2 = Mathf.Cos(angle); var c0o = c0 * stepOuterRadius; var c1o = c1 * stepOuterRadius; var s0o = s0 * stepOuterRadius; var s1o = s1 * stepOuterRadius; var o0 = new Vector2(origin.x + c0o, origin.z + s0o); var o1 = new Vector2(origin.x + c1o, origin.z + s1o); var i0 = o0; var i1 = o1; int subMeshIndex = i; for (int subDiv = 1; subDiv < smoothSubDivisions; subDiv++) { // TODO: need to space the subdivisions from smallest spaces to bigger spaces float stepMidRadius; stepMidRadius = (((outerDiameter * 0.5f) * (1.0f / (smoothSubDivisions + 1))) * ((smoothSubDivisions - 1) - (subDiv - 1))); if (subDiv == (smoothSubDivisions - 1)) { var innerRadius = (innerDiameter * 0.5f) - 0.1f; stepMidRadius = (innerRadius < 0.1f) ? stepMidRadius : innerRadius; } var c0i = c0 * stepMidRadius; var c1i = c1 * stepMidRadius; var s0i = s0 * stepMidRadius; var s1i = s1 * stepMidRadius; i0 = new Vector2(origin.x + c0i, origin.z + s0i); i1 = new Vector2(origin.x + c1i, origin.z + s1i); { var vertices = new[] { new Vector3(i0.x, maxY, i0.y), // 0 new Vector3(i0.x, minY, i0.y), // 1 new Vector3(o0.x, minY, o0.y), // 2 new Vector3(o0.x, maxY, o0.y), // 3 new Vector3(o1.x, maxY, o1.y), // 4 }; if (i == 0) { subMeshes[subMeshIndex].Polygons = CreateSquarePyramidAssetPolygons(surfaceAssets, surfaceDescriptions); } else { subMeshes[subMeshIndex].Polygons = subMeshes[subMeshIndex - i].Polygons.ToArray(); } subMeshes[subMeshIndex].HalfEdges = (anglePerStep > 0) ? invertedSquarePyramidHalfEdges.ToArray() : squarePyramidHalfEdges.ToArray(); subMeshes[subMeshIndex].Vertices = vertices; subMeshIndex++; } { var vertices = new[] { new Vector3(i0.x, maxY, i0.y), // 0 new Vector3(i0.x, minY, i0.y), // 1 new Vector3(i1.x, maxY, i1.y), // 2 new Vector3(o1.x, maxY, o1.y), // 3 }; if (i == 0) { subMeshes[subMeshIndex].Polygons = CreateTriangularPyramidAssetPolygons(surfaceAssets, surfaceDescriptions); } else { subMeshes[subMeshIndex].Polygons = subMeshes[subMeshIndex - i].Polygons.ToArray(); } subMeshes[subMeshIndex].HalfEdges = (anglePerStep > 0) ? invertedTriangularPyramidHalfEdges.ToArray() : triangularPyramidHalfEdges.ToArray(); subMeshes[subMeshIndex].Vertices = vertices; subMeshIndex++; } o0 = i0; o1 = i1; } { var vertices = new[] { new Vector3(i0.x, maxY, i0.y), // 0 new Vector3(i1.x, maxY, i1.y), // 2 new Vector3(i0.x, minY, i0.y), // 1 new Vector3(origin.x, minY, origin.y), // 3 }; if (i == 0) { subMeshes[subMeshIndex].Polygons = CreateTriangularPyramidAssetPolygons(surfaceAssets, surfaceDescriptions); } else { subMeshes[subMeshIndex].Polygons = subMeshes[subMeshIndex - i].Polygons.ToArray(); } subMeshes[subMeshIndex].HalfEdges = (anglePerStep > 0) ? invertedTriangularPyramidHalfEdges.ToArray() : triangularPyramidHalfEdges.ToArray(); subMeshes[subMeshIndex].Vertices = vertices; subMeshIndex++; } { var vertices = new[] { new Vector3(i1.x, maxY, i1.y), // 2 new Vector3(i0.x, maxY, i0.y), // 0 new Vector3(origin.x, maxY, origin.y), // 1 new Vector3(origin.x, minY, origin.y), // 3 }; if (i == 0) { subMeshes[subMeshIndex].Polygons = CreateTriangularPyramidAssetPolygons(surfaceAssets, surfaceDescriptions); } else { subMeshes[subMeshIndex].Polygons = subMeshes[subMeshIndex - i].Polygons.ToArray(); } subMeshes[subMeshIndex].HalfEdges = (anglePerStep > 0) ? invertedTriangularPyramidHalfEdges.ToArray() : triangularPyramidHalfEdges.ToArray(); subMeshes[subMeshIndex].Vertices = vertices; subMeshIndex++; } if (i == 0) { minY -= treadHeight; } minY += stepHeight; maxY += stepHeight; maxY2 += stepHeight; } } else { var minY = origin.y; var maxY = origin.y + stepHeight - treadHeight; Vector2 o0, o1; float angle = startAngle; var c1 = Mathf.Sin(angle) * stepOuterRadius; var s1 = Mathf.Cos(angle) * stepOuterRadius; for (int i = 0; i < stepCount; i++) { var c0 = c1; var s0 = s1; angle += anglePerStep; c1 = Mathf.Sin(angle) * stepOuterRadius; s1 = Mathf.Cos(angle) * stepOuterRadius; o0 = new Vector2(origin.x + c0, origin.z + s0); o1 = new Vector2(origin.x + c1, origin.z + s1); var vertices = new[] { new Vector3(origin.x, maxY, origin.z), // 0 new Vector3(o1.x, maxY, o1.y), // 1 new Vector3(o0.x, maxY, o0.y), // 2 new Vector3(origin.x, minY, origin.z), // 3 new Vector3(o1.x, minY, o1.y), // 4 new Vector3(o0.x, minY, o0.y), // 5 }; if (i == 0) { subMeshes[i].Polygons = CreateWedgeAssetPolygons(surfaceAssets, surfaceDescriptions); minY -= treadHeight; } else { subMeshes[i].Polygons = subMeshes[0].Polygons.ToArray(); } subMeshes[i].HalfEdges = (anglePerStep > 0) ? invertedWedgeHalfEdges.ToArray() : wedgeHalfEdges.ToArray(); subMeshes[i].Vertices = vertices; if (riserType != StairsRiserType.FillDown) { minY += stepHeight; } maxY += stepHeight; } } { var subMeshIndex = treadStart - cylinderSubMeshCount; var cylinderSurfaceAssets = new CSGSurfaceAsset[3] { surfaceAssets[0], surfaceAssets[1], surfaceAssets[2] }; var cylinderSurfaceDescriptions = new SurfaceDescription[outerSides + 2]; //surfaceDescriptions[0] cylinderSurfaceDescriptions[0] = surfaceDescriptions[0]; cylinderSurfaceDescriptions[1] = surfaceDescriptions[1]; for (int i = 0; i < outerSides; i++) { cylinderSurfaceDescriptions[i + 2] = surfaceDescriptions[2]; } GenerateCylinderSubMesh(subMeshes[subMeshIndex], outerDiameter, origin.y, origin.y + height, 0, outerSides, cylinderSurfaceAssets, cylinderSurfaceDescriptions); subMeshes[subMeshIndex].Operation = CSGOperationType.Intersecting; } if (haveInnerCyl) { var subMeshIndex = treadStart - 1; var cylinderSurfaceAssets = new CSGSurfaceAsset[3] { surfaceAssets[0], surfaceAssets[1], surfaceAssets[2] }; var cylinderSurfaceDescriptions = new SurfaceDescription[innerSides + 2]; //surfaceDescriptions[0] cylinderSurfaceDescriptions[0] = surfaceDescriptions[0]; cylinderSurfaceDescriptions[1] = surfaceDescriptions[1]; for (int i = 0; i < innerSides; i++) { cylinderSurfaceDescriptions[i + 2] = surfaceDescriptions[2]; } GenerateCylinderSubMesh(subMeshes[subMeshIndex], innerDiameter, origin.y, origin.y + height, 0, innerSides, cylinderSurfaceAssets, cylinderSurfaceDescriptions); subMeshes[subMeshIndex].Operation = CSGOperationType.Subtractive; } } if (haveTread) { var minY = origin.y + stepHeight - treadHeight; var maxY = origin.y + stepHeight; Vector2 i0, i1, o0, o1; float angle = startAngle; var c1 = Mathf.Sin(angle); var s1 = Mathf.Cos(angle); var startIndex = treadStart; for (int n = 0, i = startIndex; n < stepCount; n++, i++) { var c0 = c1; var s0 = s1; angle += anglePerStep; c1 = Mathf.Sin(angle); s1 = Mathf.Cos(angle); i0 = new Vector2(origin.x + (c0 * (stepInnerRadius)), origin.z + (s0 * (stepInnerRadius))); i1 = new Vector2(origin.x + (c1 * (stepInnerRadius)), origin.z + (s1 * (stepInnerRadius))); o0 = new Vector2(origin.x + (c0 * (stepOuterRadius + nosingWidth)), origin.z + (s0 * (stepOuterRadius + nosingWidth))); o1 = new Vector2(origin.x + (c1 * (stepOuterRadius + nosingWidth)), origin.z + (s1 * (stepOuterRadius + nosingWidth))); var noseSizeDeep = (new Vector2((c0 - c1), (s0 - s1)).normalized) * nosingDepth; i0 += noseSizeDeep; o0 += noseSizeDeep; var vertices = new[] { new Vector3(i1.x, maxY, i1.y), // 1 new Vector3(i0.x, maxY, i0.y), // 0 new Vector3(o0.x, maxY, o0.y), // 3 new Vector3(o1.x, maxY, o1.y), // 2 new Vector3(i1.x, minY, i1.y), // 5 new Vector3(i0.x, minY, i0.y), // 4 new Vector3(o0.x, minY, o0.y), // 7 new Vector3(o1.x, minY, o1.y), // 6 }; if (n == 0) { subMeshes[i].Polygons = CreateBoxAssetPolygons(surfaceAssets, surfaceDescriptions); } else { subMeshes[i].Polygons = subMeshes[startIndex].Polygons.ToArray(); } subMeshes[i].HalfEdges = (anglePerStep > 0) ? invertedBoxHalfEdges.ToArray() : boxHalfEdges.ToArray(); subMeshes[i].Vertices = vertices; minY += stepHeight; maxY += stepHeight; } } { var subMeshIndex = subMeshCount - cylinderSubMeshCount; var cylinderSurfaceAssets = new CSGSurfaceAsset[3] { surfaceAssets[0], surfaceAssets[1], surfaceAssets[2] }; var cylinderSurfaceDescriptions = new SurfaceDescription[outerSides + 2]; //surfaceDescriptions[0] cylinderSurfaceDescriptions[0] = surfaceDescriptions[0]; cylinderSurfaceDescriptions[1] = surfaceDescriptions[1]; for (int i = 0; i < outerSides; i++) { cylinderSurfaceDescriptions[i + 2] = surfaceDescriptions[2]; } GenerateCylinderSubMesh(subMeshes[subMeshIndex], outerDiameter + nosingWidth, origin.y, origin.y + height, 0, outerSides, cylinderSurfaceAssets, cylinderSurfaceDescriptions); subMeshes[subMeshIndex].Operation = CSGOperationType.Intersecting; } if (haveInnerCyl) { var subMeshIndex = subMeshCount - 1; var cylinderSurfaceAssets = new CSGSurfaceAsset[3] { surfaceAssets[0], surfaceAssets[1], surfaceAssets[2] }; var cylinderSurfaceDescriptions = new SurfaceDescription[innerSides + 2]; //surfaceDescriptions[0] cylinderSurfaceDescriptions[0] = surfaceDescriptions[0]; cylinderSurfaceDescriptions[1] = surfaceDescriptions[1]; for (int i = 0; i < innerSides; i++) { cylinderSurfaceDescriptions[i + 2] = surfaceDescriptions[2]; } GenerateCylinderSubMesh(subMeshes[subMeshIndex], innerDiameter - nosingWidth, origin.y, origin.y + height, 0, innerSides, cylinderSurfaceAssets, cylinderSurfaceDescriptions); subMeshes[subMeshIndex].Operation = CSGOperationType.Subtractive; } brushMeshAsset.SubMeshes = subMeshes; brushMeshAsset.CalculatePlanes(); brushMeshAsset.SetDirty(); return(true); }
// TODO: clean up public static bool GenerateSegmentedSubMesh(CSGBrushSubMesh subMesh, int horzSegments, int vertSegments, Vector3[] segmentVertices, bool topCap, bool bottomCap, int topVertex, int bottomVertex, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { // FIXME: hack, to fix math below .. vertSegments++; //if (bottomCap || topCap) // vertSegments++; int triangleCount, quadCount, capCount, extraVertices; capCount = 0; triangleCount = 0; extraVertices = 0; if (topCap) { capCount += 1; } else { extraVertices += 1; triangleCount += horzSegments; } if (bottomCap) { capCount += 1; } else { extraVertices += 1; triangleCount += horzSegments; } quadCount = horzSegments * (vertSegments - 2); var vertexCount = (horzSegments * (vertSegments - 1)) + extraVertices; var assetPolygonCount = triangleCount + quadCount + capCount; var halfEdgeCount = (triangleCount * 3) + (quadCount * 4) + (capCount * horzSegments); if (segmentVertices.Length != vertexCount) { Debug.LogError("segmentVertices.Length (" + segmentVertices.Length + ") != expectedVertexCount (" + vertexCount + ")"); subMesh.Clear(); return(false); } var vertices = segmentVertices; var polygons = new CSGBrushSubMesh.Polygon[assetPolygonCount]; var halfEdges = new BrushMesh.HalfEdge[halfEdgeCount]; var twins = new int[horzSegments]; var edgeIndex = 0; var polygonIndex = 0; var startVertex = extraVertices; var startSegment = topCap ? 1 : 0; var lastVertSegment = vertSegments - 1; var endSegment = bottomCap ? lastVertSegment : vertSegments; if (topCap) { var polygonEdgeCount = horzSegments; for (int h = 0, p = horzSegments - 1; h < horzSegments; p = h, h++) { var currEdgeIndex = edgeIndex + (horzSegments - 1) - h; halfEdges[currEdgeIndex] = new BrushMesh.HalfEdge { twinIndex = -1, vertexIndex = startVertex + (horzSegments - 1) - p }; twins[h] = currEdgeIndex; } polygons[polygonIndex] = new CSGBrushSubMesh.Polygon { surfaceID = polygonIndex, firstEdge = edgeIndex, edgeCount = polygonEdgeCount, description = surfaceDescriptions[0], surfaceAsset = surfaceAssets[0] }; edgeIndex += polygonEdgeCount; polygonIndex++; } for (int v = startSegment; v < endSegment; v++) { var startEdge = edgeIndex; for (int h = 0, p = horzSegments - 1; h < horzSegments; p = h, h++) { var n = (h + 1) % horzSegments; int polygonEdgeCount; if (v == 0) // top { // 0 // * // ^ \ // p1 /0 1\ n0 // / 2 v // *<------* // 2 t 1 polygonEdgeCount = 3; var p1 = (p * 3) + 1; var n0 = (n * 3) + 0; halfEdges[edgeIndex + 0] = new BrushMesh.HalfEdge { twinIndex = p1, vertexIndex = topVertex }; halfEdges[edgeIndex + 1] = new BrushMesh.HalfEdge { twinIndex = n0, vertexIndex = startVertex + (horzSegments - 1) - h }; halfEdges[edgeIndex + 2] = new BrushMesh.HalfEdge { twinIndex = -1, vertexIndex = startVertex + (horzSegments - 1) - p }; twins[h] = edgeIndex + 2; } else if (v == lastVertSegment) // bottom { // 0 t 1 // *------>* // ^ 1 / // p2 \0 2/ n0 // \ v // * // 2 polygonEdgeCount = 3; var p2 = startEdge + (p * 3) + 2; var n0 = startEdge + (n * 3) + 0; var t = twins[h]; halfEdges[twins[h]].twinIndex = edgeIndex + 1; halfEdges[edgeIndex + 0] = new BrushMesh.HalfEdge { twinIndex = p2, vertexIndex = startVertex + (horzSegments - 1) - p }; halfEdges[edgeIndex + 1] = new BrushMesh.HalfEdge { twinIndex = t, vertexIndex = startVertex + (horzSegments - 1) - h }; halfEdges[edgeIndex + 2] = new BrushMesh.HalfEdge { twinIndex = n0, vertexIndex = bottomVertex }; } else { // 0 t3 1 // *------>* // ^ 1 | // p1 |0 2| n0 // | 3 v // *<------* // 3 t1 2 polygonEdgeCount = 4; var p1 = startEdge + (p * 4) + 2; var n0 = startEdge + (n * 4) + 0; var t = twins[h]; halfEdges[twins[h]].twinIndex = edgeIndex + 1; halfEdges[edgeIndex + 0] = new BrushMesh.HalfEdge { twinIndex = p1, vertexIndex = startVertex + (horzSegments - 1) - p }; halfEdges[edgeIndex + 1] = new BrushMesh.HalfEdge { twinIndex = t, vertexIndex = startVertex + (horzSegments - 1) - h }; halfEdges[edgeIndex + 2] = new BrushMesh.HalfEdge { twinIndex = n0, vertexIndex = startVertex + (horzSegments - 1) - h + horzSegments }; halfEdges[edgeIndex + 3] = new BrushMesh.HalfEdge { twinIndex = -1, vertexIndex = startVertex + (horzSegments - 1) - p + horzSegments }; twins[h] = edgeIndex + 3; } polygons[polygonIndex] = new CSGBrushSubMesh.Polygon { surfaceID = polygonIndex, firstEdge = edgeIndex, edgeCount = polygonEdgeCount, description = surfaceDescriptions[0], surfaceAsset = surfaceAssets[0] }; edgeIndex += polygonEdgeCount; polygonIndex++; } if (v > 0) { startVertex += horzSegments; } } if (bottomCap) { var polygonEdgeCount = horzSegments; for (int h = 0; h < horzSegments; h++) { var currEdgeIndex = edgeIndex + h; halfEdges[twins[h]].twinIndex = currEdgeIndex; halfEdges[currEdgeIndex] = new BrushMesh.HalfEdge { twinIndex = twins[h], vertexIndex = startVertex + (horzSegments - 1) - h }; } polygons[polygonIndex] = new CSGBrushSubMesh.Polygon { surfaceID = polygonIndex, firstEdge = edgeIndex, edgeCount = polygonEdgeCount, description = surfaceDescriptions[0], surfaceAsset = surfaceAssets[0] }; } subMesh.Polygons = polygons; subMesh.HalfEdges = halfEdges; subMesh.Vertices = vertices; return(true); }
static bool CreateExtrudedSubMesh(CSGBrushSubMesh subMesh, int segments, int[] segmentDescriptionIndices, int[] segmentAssetIndices, int segmentTopIndex, int segmentBottomIndex, Vector3[] vertices, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { if (vertices.Length < 3) { return(false); } // TODO: vertex reverse winding when it's not clockwise // TODO: handle duplicate vertices, remove them or avoid them being created in the first place (maybe use indices?) var segmentTopology = new SegmentTopology[segments]; var edgeIndices = new int[segments * 2]; var polygonCount = 2; var edgeOffset = segments + segments; for (int p = segments - 2, e = segments - 1, n = 0; n < segments; p = e, e = n, n++) { // t0 // ------> -------> -----> // v0 * * v1 // <----- <------- <----- // ^ | e2 ^ | // | | | | // q1 p0| |e3 q2 e5| |n0 q3 // | | | | // | v e4 | v // ------> -------> -----> // v3 * * v2 // <----- <------- <----- // b0 // var v0 = vertices[p]; var v1 = vertices[e]; var v2 = vertices[e + segments]; var v3 = vertices[p + segments]; var equals03 = (v0 - v3).sqrMagnitude < 0.0001f; var equals12 = (v1 - v2).sqrMagnitude < 0.0001f; if (equals03) { if (equals12) { segmentTopology[n] = SegmentTopology.None; } else { segmentTopology[n] = SegmentTopology.TriangleNegative; } } else if (equals12) { segmentTopology[n] = SegmentTopology.TrianglePositive; } else { var plane = new Plane(v0, v1, v3); var dist = plane.GetDistanceToPoint(v2); if (UnityEngine.Mathf.Abs(dist) < 0.001f) { segmentTopology[n] = SegmentTopology.Quad; } else { segmentTopology[n] = (SegmentTopology)UnityEngine.Mathf.Sign(dist); } } switch (segmentTopology[n]) { case SegmentTopology.Quad: { edgeIndices[(n * 2) + 0] = edgeOffset + 1; polygonCount++; edgeOffset += 4; edgeIndices[(n * 2) + 1] = edgeOffset - 1; break; } case SegmentTopology.TrianglesNegative: case SegmentTopology.TrianglesPositive: { edgeIndices[(n * 2) + 0] = edgeOffset + 1; polygonCount += 2; edgeOffset += 6; edgeIndices[(n * 2) + 1] = edgeOffset - 1; break; } case SegmentTopology.TriangleNegative: case SegmentTopology.TrianglePositive: { edgeIndices[(n * 2) + 0] = edgeOffset + 1; polygonCount++; edgeOffset += 3; edgeIndices[(n * 2) + 1] = edgeOffset - 1; break; } case SegmentTopology.None: edgeIndices[(n * 2) + 0] = 0; edgeIndices[(n * 2) + 1] = 0; break; } } var polygons = new CSGBrushSubMesh.Polygon[polygonCount]; var surfaceDescription0 = surfaceDescriptions[segmentTopIndex]; var surfaceDescription1 = surfaceDescriptions[segmentBottomIndex]; var surfaceAsset0 = surfaceAssets[segmentTopIndex]; var surfaceAsset1 = surfaceAssets[segmentBottomIndex]; polygons[0] = new CSGBrushSubMesh.Polygon { surfaceID = 0, firstEdge = 0, edgeCount = segments, description = surfaceDescription0, surfaceAsset = surfaceAsset0 }; polygons[1] = new CSGBrushSubMesh.Polygon { surfaceID = 1, firstEdge = segments, edgeCount = segments, description = surfaceDescription1, surfaceAsset = surfaceAsset1 }; for (int s = 0, surfaceID = 2; s < segments; s++) { var descriptionIndex = (segmentDescriptionIndices == null) ? s + 2 : (segmentDescriptionIndices[s]); var assetIndex = (segmentAssetIndices == null) ? 2 : (segmentAssetIndices[s]); var firstEdge = edgeIndices[(s * 2) + 0] - 1; switch (segmentTopology[s]) { case SegmentTopology.Quad: { polygons[surfaceID] = new CSGBrushSubMesh.Polygon { surfaceID = surfaceID, firstEdge = firstEdge, edgeCount = 4, description = surfaceDescriptions[descriptionIndex], surfaceAsset = surfaceAssets[assetIndex] }; polygons[surfaceID].description.smoothingGroup = (uint)0; surfaceID++; break; } case SegmentTopology.TrianglesNegative: case SegmentTopology.TrianglesPositive: { var smoothingGroup = surfaceID + 1; // TODO: create an unique smoothing group for faceted surfaces that are split in two, // unless there's already a smoothing group for this edge; then use that polygons[surfaceID + 0] = new CSGBrushSubMesh.Polygon { surfaceID = surfaceID + 0, firstEdge = firstEdge, edgeCount = 3, description = surfaceDescriptions[descriptionIndex], surfaceAsset = surfaceAssets[assetIndex] }; polygons[surfaceID + 0].description.smoothingGroup = (uint)smoothingGroup; polygons[surfaceID + 1] = new CSGBrushSubMesh.Polygon { surfaceID = surfaceID + 1, firstEdge = firstEdge + 3, edgeCount = 3, description = surfaceDescriptions[descriptionIndex], surfaceAsset = surfaceAssets[assetIndex] }; polygons[surfaceID + 1].description.smoothingGroup = (uint)smoothingGroup; surfaceID += 2; break; } case SegmentTopology.TriangleNegative: case SegmentTopology.TrianglePositive: { polygons[surfaceID] = new CSGBrushSubMesh.Polygon { surfaceID = surfaceID, firstEdge = firstEdge, edgeCount = 3, description = surfaceDescriptions[descriptionIndex], surfaceAsset = surfaceAssets[assetIndex] }; polygons[surfaceID].description.smoothingGroup = (uint)0; surfaceID++; break; } case SegmentTopology.None: { break; } } } var halfEdges = new BrushMesh.HalfEdge[edgeOffset]; for (int p = segments - 2, e = segments - 1, n = 0; n < segments; p = e, e = n, n++) { //var vi0 = p; var vi1 = e; var vi2 = e + segments; var vi3 = p + segments; var t0 = e; var b0 = ((segments - 1) - e) + segments; switch (segmentTopology[n]) { case SegmentTopology.None: { halfEdges[t0] = new BrushMesh.HalfEdge { vertexIndex = vi2, twinIndex = b0 }; halfEdges[b0] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = t0 }; break; } case SegmentTopology.TrianglePositive: { halfEdges[t0] = new BrushMesh.HalfEdge { vertexIndex = vi2, twinIndex = -1 }; halfEdges[b0] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = -1 }; break; } case SegmentTopology.TriangleNegative: default: { halfEdges[t0] = new BrushMesh.HalfEdge { vertexIndex = vi1, twinIndex = -1 }; halfEdges[b0] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = -1 }; break; } } } for (int p = segments - 2, e = segments - 1, n = 0; n < segments; p = e, e = n, n++) { var vi0 = p; var vi1 = e; var vi2 = e + segments; var vi3 = p + segments; var t0 = e; var b0 = ((segments - 1) - e) + segments; var nn = (n + 1) % segments; var p0 = edgeIndices[(e * 2) + 1]; var n0 = edgeIndices[(nn * 2) + 0]; switch (segmentTopology[n]) { case SegmentTopology.None: { continue; } case SegmentTopology.Quad: { // t0 // ------> -------> -----> // v0 * * v1 // <----- <------- <----- // ^ | e2 ^ | // | | | | // q1 p0| |e3 q2 e5| |n0 q3 // | | | | // | v e4 | v // ------> -------> -----> // v3 * * v2 // <----- <------- <----- // b0 // var q2 = edgeIndices[(n * 2) + 0]; var e2 = q2 - 1; var e3 = q2 + 0; var e4 = q2 + 1; var e5 = q2 + 2; halfEdges[t0].twinIndex = e2; halfEdges[b0].twinIndex = e4; halfEdges[e2] = new BrushMesh.HalfEdge { vertexIndex = vi0, twinIndex = t0 }; halfEdges[e3] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = p0 }; halfEdges[e4] = new BrushMesh.HalfEdge { vertexIndex = vi2, twinIndex = b0 }; halfEdges[e5] = new BrushMesh.HalfEdge { vertexIndex = vi1, twinIndex = n0 }; break; } case SegmentTopology.TrianglesNegative: { // t0 // ------> -----------> -----> // v0 * * v1 // <----- <----------- <----- // ^ | e2 ^ ^ | // | | / / | | // | | e4/ / | | // q1 p0| |e3 / / e7| |n0 q3 // | | / /e5 | | // | | / / | | // | v/v e6 | v // ------> -----------> -----> // v3 * * v2 // <----- <----------- <----- // b0 // var q2 = edgeIndices[(n * 2) + 0]; var e2 = q2 - 1; var e3 = q2 + 0; var e4 = q2 + 1; var e5 = q2 + 2; var e6 = q2 + 3; var e7 = q2 + 4; halfEdges[t0].twinIndex = e2; halfEdges[b0].twinIndex = e6; halfEdges[e2] = new BrushMesh.HalfEdge { vertexIndex = vi0, twinIndex = t0 }; halfEdges[e3] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = p0 }; halfEdges[e4] = new BrushMesh.HalfEdge { vertexIndex = vi1, twinIndex = e5 }; halfEdges[e5] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = e4 }; halfEdges[e6] = new BrushMesh.HalfEdge { vertexIndex = vi2, twinIndex = b0 }; halfEdges[e7] = new BrushMesh.HalfEdge { vertexIndex = vi1, twinIndex = n0 }; break; } case SegmentTopology.TrianglesPositive: { // t0 // ------> -----------> -----> // v0 * * v1 // <----- <----------- <----- // ^ |^\ e5 ^ | // | | \ \ | | // | | \ \ e6 | | // q1 p0| |e3 e2\ \ e7| |n0 q3 // | | \ \ | | // | | \ \ | | // | v e4 \ v| v // ------> -----------> -----> // v3 * * v2 // <----- <----------- <----- // b0 // var q2 = edgeIndices[(n * 2) + 0]; var e2 = q2 - 1; var e3 = q2 + 0; var e4 = q2 + 1; var e5 = q2 + 2; var e6 = q2 + 3; var e7 = q2 + 4; halfEdges[t0].twinIndex = e5; halfEdges[b0].twinIndex = e4; halfEdges[e2] = new BrushMesh.HalfEdge { vertexIndex = vi0, twinIndex = e6 }; halfEdges[e3] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = p0 }; halfEdges[e4] = new BrushMesh.HalfEdge { vertexIndex = vi2, twinIndex = b0 }; halfEdges[e5] = new BrushMesh.HalfEdge { vertexIndex = vi0, twinIndex = t0 }; halfEdges[e6] = new BrushMesh.HalfEdge { vertexIndex = vi2, twinIndex = e2 }; halfEdges[e7] = new BrushMesh.HalfEdge { vertexIndex = vi1, twinIndex = n0 }; break; } case SegmentTopology.TriangleNegative: { // t0 // ---> -------> // \ ^ * v1 // <--- \ / ^ <----- // \ \ / / | | // \ \ / / | | // \ \ t0 / / | | // \ \ / /e2 | | n0 // \ \ / / | | // \ \ / / q2 e4| | q3 // \ \ / / | | // v \/ v e3 | v // ------------> -------> -----> // v3 * * v2 // <----------- <------- <----- // b0 // var q2 = edgeIndices[(n * 2) + 0]; var e2 = q2 - 1; var e3 = q2 + 0; var e4 = q2 + 1; halfEdges[t0].twinIndex = e2; halfEdges[b0].twinIndex = e3; // vi0 / vi3 halfEdges[e2] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = t0 }; halfEdges[e3] = new BrushMesh.HalfEdge { vertexIndex = vi2, twinIndex = b0 }; halfEdges[e4] = new BrushMesh.HalfEdge { vertexIndex = vi1, twinIndex = n0 }; break; } case SegmentTopology.TrianglePositive: { // // -------> ----> // v0 * \ ^ // <----- \ / <--- // ^ |^ \ / / // | | \ \ / / // | | \ \ / / // | | \ \ t0 / / // q1 | | e2 \ \ / / // | | \ \ / / // p0| |e3 \ \ / / // | | q2 \ \ / / // | | \ \ / / // | v e4 \ v / v // ------> -----------> -----> // v3 * * v2 // <----- <----------- <----- // b0 // var q2 = edgeIndices[(n * 2) + 0]; var e2 = q2 - 1; var e3 = q2 + 0; var e4 = q2 + 1; halfEdges[t0].twinIndex = e2; halfEdges[b0].twinIndex = e4; halfEdges[e2] = new BrushMesh.HalfEdge { vertexIndex = vi0, twinIndex = t0 }; halfEdges[e3] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = p0 }; // vi1 / vi2 halfEdges[e4] = new BrushMesh.HalfEdge { vertexIndex = vi2, twinIndex = b0 }; break; } } } subMesh.Polygons = polygons; subMesh.HalfEdges = halfEdges; subMesh.Vertices = vertices; subMesh.CreateOrUpdateBrushMesh(); return(true); }
public static bool GenerateExtrudedShapeAsset(CSGBrushMeshAsset brushMeshAsset, Curve2D shape, Path path, int curveSegments, CSGSurfaceAsset[] surfaceAssets, ref SurfaceDescription[] surfaceDescriptions) { var shapeVertices = new List <Vector2>(); var shapeSegmentIndices = new List <int>(); GetPathVertices(shape, curveSegments, shapeVertices, shapeSegmentIndices); Vector2[][] polygonVerticesArray; int[][] polygonIndicesArray; if (!Decomposition.ConvexPartition(shapeVertices, shapeSegmentIndices, out polygonVerticesArray, out polygonIndicesArray)) { return(false); } // TODO: make each extruded quad split into two triangles when it's not a perfect plane, // split it to make sure it's convex // TODO: make it possible to smooth (parts) of the shape // TODO: make materials work well // TODO: make it possible to 'draw' shapes on any surface // TODO: make path work as a spline, with subdivisions // TODO: make this work well with twisted rotations // TODO: make shape/path subdivisions be configurable / automatic var subMeshes = new List <CSGBrushSubMesh>(); for (int p = 0; p < polygonVerticesArray.Length; p++) { var polygonVertices = polygonVerticesArray[p]; var segmentIndices = polygonIndicesArray[p]; var shapeSegments = polygonVertices.Length; for (int s = 0; s < path.segments.Length - 1; s++) { var pathPointA = path.segments[s]; var pathPointB = path.segments[s + 1]; int subSegments = 1; var offsetQuaternion = pathPointB.rotation * Quaternion.Inverse(pathPointA.rotation); var offsetEuler = offsetQuaternion.eulerAngles; if (offsetEuler.x > 180) { offsetEuler.x = 360 - offsetEuler.x; } if (offsetEuler.y > 180) { offsetEuler.y = 360 - offsetEuler.y; } if (offsetEuler.z > 180) { offsetEuler.z = 360 - offsetEuler.z; } var maxAngle = Mathf.Max(offsetEuler.x, offsetEuler.y, offsetEuler.z); if (maxAngle != 0) { subSegments = Mathf.Max(1, (int)Mathf.Ceil(maxAngle / 5)); } if ((pathPointA.scale.x / pathPointA.scale.y) != (pathPointB.scale.x / pathPointB.scale.y) && (subSegments & 1) == 1) { subSegments += 1; } for (int n = 0; n < subSegments; n++) { var matrix0 = PathPoint.Lerp(ref path.segments[s], ref path.segments[s + 1], n / (float)subSegments); var matrix1 = PathPoint.Lerp(ref path.segments[s], ref path.segments[s + 1], (n + 1) / (float)subSegments); // TODO: this doesn't work if top and bottom polygons intersect // => need to split into two brushes then, invert one of the two brushes var invertDot = Vector3.Dot(matrix0.MultiplyVector(Vector3.forward).normalized, (matrix1.MultiplyPoint(shapeVertices[0]) - matrix0.MultiplyPoint(shapeVertices[0])).normalized); if (invertDot == 0.0f) { continue; } Vector3[] vertices; if (invertDot < 0) { var m = matrix0; matrix0 = matrix1; matrix1 = m; } if (!GetExtrudedVertices(polygonVertices, matrix0, matrix1, out vertices)) { continue; } var subMesh = new CSGBrushSubMesh(); CreateExtrudedSubMesh(subMesh, shapeSegments, segmentIndices, 0, 1, vertices, surfaceAssets, surfaceDescriptions); subMeshes.Add(subMesh); } } } brushMeshAsset.SubMeshes = subMeshes.ToArray(); brushMeshAsset.CalculatePlanes(); brushMeshAsset.OnValidate(); brushMeshAsset.SetDirty(); return(true); }
static void CreateExtrudedSubMesh(CSGBrushSubMesh subMesh, Vector3[] sideVertices, Vector3 extrusion, int[] segmentDescriptionIndices, int[] segmentAssetIndices, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { const float distanceEpsilon = 0.0000001f; for (int i = sideVertices.Length - 1; i >= 0; i--) { var j = (i - 1 + sideVertices.Length) % sideVertices.Length; var magnitude = (sideVertices[j] - sideVertices[i]).sqrMagnitude; if (magnitude < distanceEpsilon) { // TODO: improve on this var tmp = sideVertices.ToList(); tmp.RemoveAt(i); sideVertices = tmp.ToArray(); } } var segments = sideVertices.Length; var isSegmentConvex = new sbyte[segments]; var edgeIndices = new int[segments * 2]; var vertices = new Vector3[segments * 2]; var polygonCount = 2; var edgeOffset = segments + segments; for (int p = segments - 2, e = segments - 1, n = 0; n < segments; p = e, e = n, n++) { // t0 // ------> -------> -----> // v0 * * v1 // <----- <------- <----- // ^ | e2 ^ | // | | | | // q1 p0| |e3 q2 e5| |n0 q3 // | | | | // | v e4 | v // ------> -------> -----> // v3 * * v2 // <----- <------- <----- // b0 // var v0 = sideVertices[p]; var v1 = sideVertices[e]; var v2 = sideVertices[e] + extrusion; var v3 = sideVertices[p] + extrusion; vertices[p] = v0; vertices[e] = v1; vertices[e + segments] = v2; vertices[p + segments] = v3; var plane = new Plane(v0, v1, v3); var dist = plane.GetDistanceToPoint(v2); if (UnityEngine.Mathf.Abs(dist) < 0.001f) { isSegmentConvex[n] = 0; } else { isSegmentConvex[n] = (sbyte)UnityEngine.Mathf.Sign(dist); } edgeIndices[(n * 2) + 0] = edgeOffset + 1; if (isSegmentConvex[n] == 0) { polygonCount++; edgeOffset += 4; } else { polygonCount += 2; edgeOffset += 6; } edgeIndices[(n * 2) + 1] = edgeOffset - 1; } var polygons = new CSGBrushSubMesh.Polygon[polygonCount]; var descriptionIndex0 = (segmentDescriptionIndices == null) ? 0 : (segmentDescriptionIndices[0]); var descriptionIndex1 = (segmentDescriptionIndices == null) ? 1 : (segmentDescriptionIndices[1]); var assetIndex0 = (segmentAssetIndices == null) ? 0 : (segmentAssetIndices[0]); var assetIndex1 = (segmentAssetIndices == null) ? 1 : (segmentAssetIndices[1]); var surfaceDescription0 = surfaceDescriptions[descriptionIndex0]; var surfaceDescription1 = surfaceDescriptions[descriptionIndex1]; var surfaceAsset0 = surfaceAssets[assetIndex0]; var surfaceAsset1 = surfaceAssets[assetIndex1]; polygons[0] = new CSGBrushSubMesh.Polygon { surfaceID = 0, firstEdge = 0, edgeCount = segments, description = surfaceDescription0, surfaceAsset = surfaceAsset0 }; polygons[1] = new CSGBrushSubMesh.Polygon { surfaceID = 1, firstEdge = segments, edgeCount = segments, description = surfaceDescription1, surfaceAsset = surfaceAsset1 }; for (int s = 0, surfaceID = 2; s < segments; s++) { var descriptionIndex = (segmentDescriptionIndices == null) ? s + 2 : (segmentDescriptionIndices[s + 2]); var assetIndex = (segmentAssetIndices == null) ? 2 : (segmentAssetIndices[s + 2]); var firstEdge = edgeIndices[(s * 2) + 0] - 1; if (isSegmentConvex[s] == 0) { polygons[surfaceID] = new CSGBrushSubMesh.Polygon { surfaceID = surfaceID, firstEdge = firstEdge, edgeCount = 4, description = surfaceDescriptions[descriptionIndex], surfaceAsset = surfaceAssets[assetIndex] }; polygons[surfaceID].description.smoothingGroup = (uint)0; surfaceID++; } else { var smoothingGroup = surfaceID + 1; // TODO: create an unique smoothing group for faceted surfaces that are split in two, // unless there's already a smoothing group for this edge; then use that polygons[surfaceID + 0] = new CSGBrushSubMesh.Polygon { surfaceID = surfaceID + 0, firstEdge = firstEdge, edgeCount = 3, description = surfaceDescriptions[descriptionIndex], surfaceAsset = surfaceAssets[assetIndex] }; polygons[surfaceID + 0].description.smoothingGroup = (uint)smoothingGroup; polygons[surfaceID + 1] = new CSGBrushSubMesh.Polygon { surfaceID = surfaceID + 1, firstEdge = firstEdge + 3, edgeCount = 3, description = surfaceDescriptions[descriptionIndex], surfaceAsset = surfaceAssets[assetIndex] }; polygons[surfaceID + 1].description.smoothingGroup = (uint)smoothingGroup; surfaceID += 2; } } var halfEdges = new BrushMesh.HalfEdge[edgeOffset]; for (int p = segments - 2, e = segments - 1, n = 0; n < segments; p = e, e = n, n++) { var vi1 = e; var vi3 = p + segments; var t0 = e; var b0 = ((segments - 1) - e) + segments; halfEdges[t0] = new BrushMesh.HalfEdge { vertexIndex = vi1, twinIndex = -1 }; halfEdges[b0] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = -1 }; } for (int p = segments - 2, e = segments - 1, n = 0; n < segments; p = e, e = n, n++) { var vi0 = p; var vi1 = e; var vi2 = e + segments; var vi3 = p + segments; var t0 = e; var b0 = ((segments - 1) - e) + segments; var p0 = edgeIndices[(p * 2) + 1]; var n0 = edgeIndices[(n * 2) + 0]; if (isSegmentConvex[e] == 0) { // t0 // ------> -------> -----> // v0 * * v1 // <----- <------- <----- // ^ | e2 ^ | // | | | | // q1 p0| |e3 q2 e5| |n0 q3 // | | | | // | v e4 | v // ------> -------> -----> // v3 * * v2 // <----- <------- <----- // b0 // var q2 = edgeIndices[(e * 2) + 0]; var e2 = q2 - 1; var e3 = q2 + 0; var e4 = q2 + 1; var e5 = q2 + 2; halfEdges[t0].twinIndex = e2; halfEdges[b0].twinIndex = e4; halfEdges[e2] = new BrushMesh.HalfEdge { vertexIndex = vi0, twinIndex = t0 }; halfEdges[e3] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = p0 }; halfEdges[e4] = new BrushMesh.HalfEdge { vertexIndex = vi2, twinIndex = b0 }; halfEdges[e5] = new BrushMesh.HalfEdge { vertexIndex = vi1, twinIndex = n0 }; } else if (isSegmentConvex[e] == -1) { // t0 // ------> -----------> -----> // v0 * * v1 // <----- <----------- <----- // ^ | e2 ^ ^ | // | | / / | | // | | e4/ / | | // q1 p0| |e3 / / e7| |n0 q3 // | | / /e5 | | // | | / / | | // | v/v e6 | v // ------> -----------> -----> // v3 * * v2 // <----- <----------- <----- // b0 // var q2 = edgeIndices[(e * 2) + 0]; var e2 = q2 - 1; var e3 = q2 + 0; var e4 = q2 + 1; var e5 = q2 + 2; var e6 = q2 + 3; var e7 = q2 + 4; halfEdges[t0].twinIndex = e2; halfEdges[b0].twinIndex = e6; halfEdges[e2] = new BrushMesh.HalfEdge { vertexIndex = vi0, twinIndex = t0 }; halfEdges[e3] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = p0 }; halfEdges[e4] = new BrushMesh.HalfEdge { vertexIndex = vi1, twinIndex = e5 }; halfEdges[e5] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = e4 }; halfEdges[e6] = new BrushMesh.HalfEdge { vertexIndex = vi2, twinIndex = b0 }; halfEdges[e7] = new BrushMesh.HalfEdge { vertexIndex = vi1, twinIndex = n0 }; } else if (isSegmentConvex[e] == 1) { // t0 // ------> -----------> -----> // v0 * * v1 // <----- <----------- <----- // ^ |^\ e5 ^ | // | | \ \ | | // | | \ \ e6 | | // q1 p0| |e3 e2\ \ e7| |n0 q3 // | | \ \ | | // | | \ \ | | // | v e4 \ v| v // ------> -----------> -----> // v3 * * v2 // <----- <----------- <----- // b0 // var q2 = edgeIndices[(e * 2) + 0]; var e2 = q2 - 1; var e3 = q2 + 0; var e4 = q2 + 1; var e5 = q2 + 2; var e6 = q2 + 3; var e7 = q2 + 4; halfEdges[t0].twinIndex = e5; halfEdges[b0].twinIndex = e4; halfEdges[e2] = new BrushMesh.HalfEdge { vertexIndex = vi0, twinIndex = e6 }; halfEdges[e3] = new BrushMesh.HalfEdge { vertexIndex = vi3, twinIndex = p0 }; halfEdges[e4] = new BrushMesh.HalfEdge { vertexIndex = vi2, twinIndex = b0 }; halfEdges[e5] = new BrushMesh.HalfEdge { vertexIndex = vi0, twinIndex = t0 }; halfEdges[e6] = new BrushMesh.HalfEdge { vertexIndex = vi2, twinIndex = e2 }; halfEdges[e7] = new BrushMesh.HalfEdge { vertexIndex = vi1, twinIndex = n0 }; } } subMesh.Polygons = polygons; subMesh.HalfEdges = halfEdges; subMesh.Vertices = vertices; subMesh.CreateOrUpdateBrushMesh(); }