void HandleDuplication() { #if UNITY_EDITOR { var currentInstanceID = this.GetInstanceID(); if (instanceID == 0) { instanceID = currentInstanceID; genGuidHashCode = Guid.NewGuid().GetHashCode(); } else if (instanceID != currentInstanceID) { var prevObject = UnityEditor.EditorUtility.InstanceIDToObject(instanceID) as CSGGeneratorComponent; // if our stored instanceID is the same as an existing generator and has the same guid, // we can assume we've been duplicated if (prevObject && prevObject.genGuidHashCode == genGuidHashCode) { if (prevObject.brushMeshAsset == brushMeshAsset) { brushMeshAsset = Instantiate(brushMeshAsset); } genGuidHashCode = Guid.NewGuid().GetHashCode(); } instanceID = currentInstanceID; } } #endif }
public static bool GenerateBoxAsset(CSGBrushMeshAsset brushMeshAsset, UnityEngine.Vector3 min, UnityEngine.Vector3 max, CSGSurfaceAsset[] surfaceAssets, SurfaceFlags surfaceFlags = SurfaceFlags.None) { if (!BoundsExtensions.IsValid(min, max)) { brushMeshAsset.Clear(); return(false); } if (surfaceAssets.Length != 6) { brushMeshAsset.Clear(); 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; } brushMeshAsset.Polygons = CreateBoxAssetPolygons(surfaceAssets, surfaceFlags); brushMeshAsset.HalfEdges = boxHalfEdges.ToArray(); brushMeshAsset.Vertices = BrushMeshFactory.CreateBoxVertices(min, max); brushMeshAsset.CalculatePlanes(); brushMeshAsset.SetDirty(); 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 SurfaceReference(CSGNode node, CSGBrushMeshAsset brushMeshAsset, int subNodeIndex, int subMeshIndex, int surfaceIndex, int surfaceID) { this.node = node; this.brushMeshAsset = brushMeshAsset; this.subNodeIndex = subNodeIndex; this.subMeshIndex = subMeshIndex; this.surfaceIndex = surfaceIndex; this.surfaceID = surfaceID; }
public static bool GenerateConeAsset(CSGBrushMeshAsset brushMeshAsset, CSGCircleDefinition bottom, float topHeight, float rotation, int sides, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { CSGCircleDefinition top; top.diameterX = 0; top.diameterZ = 0; top.height = topHeight; return(GenerateConicalFrustumAsset(brushMeshAsset, bottom, top, rotation, sides, surfaceAssets, surfaceDescriptions)); }
public virtual void UpdateGenerator() { // BrushMeshes of generators must always be unique if (!brushMeshAsset || !CSGBrushMeshAssetManager.IsBrushMeshUnique(brushMeshAsset)) { brushMeshAsset = UnityEngine.ScriptableObject.CreateInstance <CSGBrushMeshAsset>(); brushMeshAsset.name = "Generated " + NodeTypeName; } UpdateGeneratorInternal(); UpdateBrushMeshInstances(); }
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 GenerateBoxAsset(CSGBrushMeshAsset brushMeshAsset, UnityEngine.Vector3 min, UnityEngine.Vector3 max, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { if (!BoundsExtensions.IsValid(min, max)) { brushMeshAsset.Clear(); Debug.LogError("bounds is of an invalid size " + (max - min)); return(false); } if (surfaceDescriptions == null || surfaceDescriptions.Length != 6) { brushMeshAsset.Clear(); Debug.LogError("surfaceDescriptions needs to be an array of length 6"); return(false); } if (surfaceAssets == null || surfaceAssets.Length != 6) { brushMeshAsset.Clear(); Debug.LogError("surfaceAssets needs to be an array of 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; } brushMeshAsset.Polygons = CreateBoxAssetPolygons(surfaceAssets, surfaceDescriptions); brushMeshAsset.HalfEdges = boxHalfEdges.ToArray(); brushMeshAsset.Vertices = BrushMeshFactory.CreateBoxVertices(min, max); brushMeshAsset.CalculatePlanes(); brushMeshAsset.SetDirty(); return(true); }
public static bool GenerateStadiumAsset(CSGBrushMeshAsset brushMeshAsset, CSGStadiumDefinition definition) { Vector3[] vertices = null; if (!GenerateStadiumVertices(definition, ref vertices)) { brushMeshAsset.Clear(); return(false); } var subMeshes = new[] { new CSGBrushSubMesh() }; var surfaceIndices = new int[vertices.Length + 2]; CreateExtrudedSubMesh(subMeshes[0], definition.sides, surfaceIndices, surfaceIndices, 0, 1, vertices, definition.surfaceAssets, definition.surfaceDescriptions); brushMeshAsset.SubMeshes = subMeshes; brushMeshAsset.CalculatePlanes(); brushMeshAsset.SetDirty(); return(true); }
public static bool GenerateCylinderAsset(CSGBrushMeshAsset brushMeshAsset, CSGCylinderDefinition definition) { definition.Validate(); var tempTop = definition.top; var tempBottom = definition.bottom; if (!definition.isEllipsoid) { tempTop.diameterZ = tempTop.diameterX; tempBottom.diameterZ = tempBottom.diameterX; } switch (definition.type) { case CylinderShapeType.Cylinder: return(BrushMeshAssetFactory.GenerateCylinderAsset(brushMeshAsset, tempBottom, tempTop.height, definition.rotation, definition.sides, definition.surfaceAssets, definition.surfaceDescriptions)); case CylinderShapeType.ConicalFrustum: return(BrushMeshAssetFactory.GenerateConicalFrustumAsset(brushMeshAsset, tempBottom, tempTop, definition.rotation, definition.sides, definition.surfaceAssets, definition.surfaceDescriptions)); case CylinderShapeType.Cone: return(BrushMeshAssetFactory.GenerateConeAsset(brushMeshAsset, tempBottom, tempTop.height, definition.rotation, definition.sides, definition.surfaceAssets, definition.surfaceDescriptions)); } return(false); }
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 GenerateCapsuleAsset(CSGBrushMeshAsset brushMeshAsset, ref CSGCapsuleDefinition definition) { Vector3[] vertices = null; if (!GenerateCapsuleVertices(ref definition, ref vertices)) { brushMeshAsset.Clear(); return(false); } // TODO: share this with GenerateCapsuleVertices var bottomCap = !definition.haveRoundedBottom; var topCap = !definition.haveRoundedTop; var sides = definition.sides; var segments = definition.segments; var bottomVertex = definition.bottomVertex; var topVertex = definition.topVertex; var subMeshes = new[] { new CSGBrushSubMesh() }; if (!GenerateSegmentedSubMesh(subMeshes[0], sides, segments, vertices, topCap, bottomCap, topVertex, bottomVertex, definition.surfaceAssets, definition.surfaceDescriptions)) { brushMeshAsset.Clear(); return(false); } brushMeshAsset.SubMeshes = subMeshes; brushMeshAsset.CalculatePlanes(); brushMeshAsset.SetDirty(); return(true); }
public static bool GenerateBoxAsset(CSGBrushMeshAsset brushMeshAsset, UnityEngine.Bounds bounds, CSGSurfaceAsset[] surfaceAssets, SurfaceDescription[] surfaceDescriptions) { return(GenerateBoxAsset(brushMeshAsset, bounds.min, bounds.max, surfaceAssets, surfaceDescriptions)); }
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); }
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 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); }
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); }
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: create helper method to cut brushes, use that instead of intersection + subtraction brushes // TODO: create spiral sides support public static bool GenerateSpiralStairsAsset(CSGBrushMeshAsset brushMeshAsset, ref CSGSpiralStairsDefinition definition) { return(GenerateSpiralStairsAsset(brushMeshAsset, ref definition, definition.surfaceAssets, ref definition.surfaceDescriptions)); }