public override void PerformWeld() { // New position for the vertices is their center Vector3 newPosition = Vector3.zero; for (int i = 0; i < sourceVertices.Count; i++) { newPosition += sourceVertices[i].Position; } newPosition /= sourceVertices.Count; // Update all the selected vertices UVs for (int i = 0; i < sourceVertices.Count; i++) { Polygon polygon = vertexPolygonMappings[sourceVertices[i]]; sourceVertices[i].UV = GeometryHelper.GetUVForPosition(polygon, newPosition); } // Update all the selected vertices to their new position for (int i = 0; i < sourceVertices.Count; i++) { sourceVertices[i].Position = newPosition; } }
internal static bool SplitPolygonsByPlane(List <Polygon> polygons, // Source polygons that will be split Plane splitPlane, bool excludeNewPolygons, // Whether new polygons should be marked as excludeFromBuild out List <Polygon> polygonsFront, out List <Polygon> polygonsBack) { polygonsFront = new List <Polygon>(); polygonsBack = new List <Polygon>(); // First of all make sure splitting actually needs to occur (we'll get bad issues if // we try splitting geometry when we don't need to) if (!GeometryHelper.PolygonsIntersectPlane(polygons, splitPlane)) { return(false); } // These are the vertices that will be used in the new caps List <Vertex> newVertices = new List <Vertex>(); for (int polygonIndex = 0; polygonIndex < polygons.Count; polygonIndex++) { Polygon.PolygonPlaneRelation planeRelation = Polygon.TestPolygonAgainstPlane(polygons[polygonIndex], splitPlane); // Polygon has been found to span both sides of the plane, attempt to split into two pieces if (planeRelation == Polygon.PolygonPlaneRelation.Spanning) { Polygon frontPolygon; Polygon backPolygon; Vertex newVertex1; Vertex newVertex2; // Attempt to split the polygon if (Polygon.SplitPolygon(polygons[polygonIndex], out frontPolygon, out backPolygon, out newVertex1, out newVertex2, splitPlane)) { // If the split algorithm was successful (produced two valid polygons) then add each polygon to // their respective points and track the intersection points polygonsFront.Add(frontPolygon); polygonsBack.Add(backPolygon); newVertices.Add(newVertex1); newVertices.Add(newVertex2); } else { // Two valid polygons weren't generated, so use the valid one if (frontPolygon != null) { planeRelation = Polygon.PolygonPlaneRelation.InFront; } else if (backPolygon != null) { planeRelation = Polygon.PolygonPlaneRelation.Behind; } else { planeRelation = Polygon.PolygonPlaneRelation.InFront; Debug.LogError("Polygon splitting has resulted in two zero area polygons. This is unhandled."); // Polygon.PolygonPlaneRelation secondplaneRelation = Polygon.TestPolygonAgainstPlane(polygons[polygonIndex], splitPlane); } } } // If the polygon is on one side of the plane or the other if (planeRelation != Polygon.PolygonPlaneRelation.Spanning) { // Make sure any points that are coplanar on non-straddling polygons are still used in polygon // construction for (int vertexIndex = 0; vertexIndex < polygons[polygonIndex].Vertices.Length; vertexIndex++) { if (Polygon.ComparePointToPlane(polygons[polygonIndex].Vertices[vertexIndex].Position, splitPlane) == Polygon.PointPlaneRelation.On) // if(Polygon.ComparePointToPlane2(polygons[polygonIndex].Vertices[vertexIndex].Position, splitPlane) == Polygon.PointPlaneRelation.On) { newVertices.Add(polygons[polygonIndex].Vertices[vertexIndex]); } } if (planeRelation == Polygon.PolygonPlaneRelation.Behind) { polygonsBack.Add(polygons[polygonIndex]); } else { polygonsFront.Add(polygons[polygonIndex]); } } } // If any splits occured or coplanar vertices are found. (For example if you're splitting a sphere at the // equator then no polygons will be split but there will be a bunch of coplanar vertices!) if (newVertices.Count > 0 && polygonsBack.Count >= 3 && polygonsFront.Count >= 3) { // HACK: This code is awful, because we end up with lots of duplicate vertices List <Vector3> positions = new List <Vector3>(newVertices.Count); for (int i = 0; i < newVertices.Count; i++) { positions.Add(newVertices[i].Position); } Polygon newPolygon = PolygonFactory.ConstructPolygon(positions, true); // Assuming it was possible to create a polygon if (newPolygon != null) { if (!MathHelper.PlaneEqualsLooser(newPolygon.Plane, splitPlane)) { // Polygons are sometimes constructed facing the wrong way, possibly due to a winding order // mismatch. If the two normals are opposite, flip the new polygon if (Vector3.Dot(newPolygon.Plane.normal, splitPlane.normal) < -0.9f) { newPolygon.Flip(); } } newPolygon.ExcludeFromFinal = excludeNewPolygons; polygonsFront.Add(newPolygon); newPolygon = newPolygon.DeepCopy(); newPolygon.Flip(); newPolygon.ExcludeFromFinal = excludeNewPolygons; if (newPolygon.Plane.normal == Vector3.zero) { Debug.LogError("Invalid Normal! Shouldn't be zero. This is unexpected since extraneous positions should have been removed!"); // Polygon fooNewPolygon = PolygonFactory.ConstructPolygon(positions, true); } polygonsBack.Add(newPolygon); } return(true); } else { // It wasn't possible to create the polygon, for example the constructed polygon was too small // This could happen if you attempt to clip the tip off a long but thin brush, the plane-polyhedron test // would say they intersect but in reality the resulting polygon would be near zero area return(false); } }
public void DrawBrushTypeField() { GUILayout.BeginHorizontal(); PrimitiveBrushType[] selectedTypes = BrushTargets.Select(item => item.BrushType).ToArray(); if (overridenBrushType.HasValue) { selectedTypes = new PrimitiveBrushType[] { overridenBrushType.Value }; } PrimitiveBrushType?newType = SabreGUILayout.EnumPopupMixed("Brush Type", selectedTypes); if (newType.HasValue) { overridenBrushType = newType; if (newType.Value == PrimitiveBrushType.Prism) { GUILayout.Label("Sides", SabreGUILayout.GetForeStyle(), GUILayout.Width(30)); EditorGUILayout.PropertyField(prismSideCountProp, new GUIContent("")); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); } else if (newType.Value == PrimitiveBrushType.Cylinder) { GUILayout.Label("Sides", SabreGUILayout.GetForeStyle(), GUILayout.Width(30)); EditorGUILayout.PropertyField(cylinderSideCountProp, new GUIContent("")); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); } else if (newType.Value == PrimitiveBrushType.Sphere) { GUILayout.Label("Sides", SabreGUILayout.GetForeStyle(), GUILayout.Width(30)); EditorGUILayout.PropertyField(sphereSideCountProp, new GUIContent("")); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); } } if (GUILayout.Button("Reset Polygons")) { Undo.RecordObjects(targets, "Reset Polygons"); foreach (var thisBrush in targets) { if (overridenBrushType.HasValue) { ((PrimitiveBrush)thisBrush).BrushType = overridenBrushType.Value; } ((PrimitiveBrush)thisBrush).ResetPolygons(); ((PrimitiveBrush)thisBrush).Invalidate(true); } overridenBrushType = null; } GUILayout.EndHorizontal(); if (GUILayout.Button("Shell")) { List <GameObject> newSelection = new List <GameObject>(); foreach (var thisBrush in targets) { GameObject newObject = ((PrimitiveBrush)thisBrush).Duplicate(); Polygon[] polygons = newObject.GetComponent <PrimitiveBrush>().GetPolygons(); VertexUtility.DisplacePolygons(polygons, -CurrentSettings.PositionSnapDistance); Bounds newBounds = newObject.GetComponent <PrimitiveBrush>().GetBounds(); // Verify the new geometry if (GeometryHelper.IsBrushConvex(polygons) && newBounds.GetSmallestExtent() > 0) { Undo.RegisterCreatedObjectUndo(newObject, "Shell"); newSelection.Add(newObject); } else { // Produced a concave brush, delete it and pretend nothing happened GameObject.DestroyImmediate(newObject); Debug.LogWarning("Could not shell " + thisBrush.name + " as shelled geometry would not be valid. Try lowering Pos Snapping and attempt Shell again."); } } if (newSelection.Count > 0) { Selection.objects = newSelection.ToArray(); } } }
public void SnapSelectedVertices(bool isAbsoluteGrid) { // So we know which polygons need to have their normals recalculated List <Polygon> affectedPolygons = new List <Polygon>(); foreach (PrimitiveBrush brush in targetBrushes) { Polygon[] polygons = brush.GetPolygons(); for (int i = 0; i < polygons.Length; i++) { Polygon polygon = polygons[i]; int vertexCount = polygon.Vertices.Length; Vector3[] newPositions = new Vector3[vertexCount]; Vector2[] newUV = new Vector2[vertexCount]; for (int j = 0; j < vertexCount; j++) { newPositions[j] = polygon.Vertices[j].Position; newUV[j] = polygon.Vertices[j].UV; } bool polygonAffected = false; for (int j = 0; j < vertexCount; j++) { Vertex vertex = polygon.Vertices[j]; if (selectedVertices.ContainsKey(vertex)) { Vector3 newPosition = vertex.Position; float snapDistance = CurrentSettings.PositionSnapDistance; if (isAbsoluteGrid) { newPosition = brush.transform.TransformPoint(newPosition); } newPosition = MathHelper.RoundVector3(newPosition, snapDistance); if (isAbsoluteGrid) { newPosition = brush.transform.InverseTransformPoint(newPosition); } newPositions[j] = newPosition; newUV[j] = GeometryHelper.GetUVForPosition(polygon, newPosition); polygonAffected = true; } } if (polygonAffected) { affectedPolygons.Add(polygon); } // Apply all the changes to the polygon for (int j = 0; j < vertexCount; j++) { Vertex vertex = polygon.Vertices[j]; vertex.Position = newPositions[j]; vertex.UV = newUV[j]; } polygon.CalculatePlane(); } } if (affectedPolygons.Count > 0) { for (int i = 0; i < affectedPolygons.Count; i++) { affectedPolygons[i].ResetVertexNormals(); } foreach (PrimitiveBrush brush in targetBrushes) { brush.Invalidate(true); brush.BreakTypeRelation(); } } }
public override void OnInspectorGUI() { // DrawDefaultInspector(); DrawBrushTypeField(); // BrushOrder brushOrder = BrushTarget.GetBrushOrder(); // string positionString = string.Join(",", brushOrder.Position.Select(item => item.ToString()).ToArray()); // GUILayout.Label(positionString, EditorStyles.boldLabel); // List<BrushCache> intersections = BrushTarget.BrushCache.IntersectingVisualBrushCaches; // // for (int i = 0; i < intersections.Count; i++) // { // GUILayout.Label(intersections[i].Mode.ToString(), EditorStyles.boldLabel); // } GUILayout.BeginHorizontal(); GUI.SetNextControlName("rescaleTextbox"); rescaleString = EditorGUILayout.TextField(rescaleString); bool keyboardEnter = Event.current.isKey && Event.current.keyCode == KeyCode.Return && Event.current.type == EventType.KeyUp && GUI.GetNameOfFocusedControl() == "rescaleTextbox"; if (GUILayout.Button("Rescale") || keyboardEnter) { // Try to parse a Vector3 scale from the input string Vector3 rescaleVector3; if (StringHelper.TryParseScale(rescaleString, out rescaleVector3)) { // None of the scale components can be zero if (rescaleVector3.x != 0 && rescaleVector3.y != 0 && rescaleVector3.z != 0) { // Rescale all the brushes Undo.RecordObjects(targets, "Rescale Polygons"); foreach (var thisBrush in targets) { BrushUtility.Rescale((PrimitiveBrush)thisBrush, rescaleVector3); } } } } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUI.SetNextControlName("resizeTextbox"); resizeString = EditorGUILayout.TextField(resizeString); keyboardEnter = Event.current.isKey && Event.current.keyCode == KeyCode.Return && Event.current.type == EventType.KeyUp && GUI.GetNameOfFocusedControl() == "resizeTextbox"; if (GUILayout.Button("Resize") || keyboardEnter) { // Try to parse a Vector3 scale from the input string Vector3 resizeVector3; if (StringHelper.TryParseScale(resizeString, out resizeVector3)) { // None of the size components can be zero if (resizeVector3.x != 0 && resizeVector3.y != 0 && resizeVector3.z != 0) { // Rescale all the brushes so that the local bounds is the same size as the resize vector Undo.RecordObjects(targets, "Resize Polygons"); PrimitiveBrush[] brushes = BrushTargets; foreach (PrimitiveBrush brush in brushes) { BrushUtility.Resize(brush, resizeVector3); } } } } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); sourceMesh = EditorGUILayout.ObjectField(sourceMesh, typeof(Mesh), false) as Mesh; if (GUILayout.Button("Import")) { if (sourceMesh != null) { Undo.RecordObjects(targets, "Import Polygons From Mesh"); Polygon[] polygons = BrushFactory.GeneratePolygonsFromMesh(sourceMesh).ToArray(); bool convex = GeometryHelper.IsBrushConvex(polygons); if (!convex) { Debug.LogError("Concavities detected in imported mesh. This may result in issues during CSG, please change the source geometry so that it is convex"); } foreach (var thisBrush in targets) { ((PrimitiveBrush)thisBrush).SetPolygons(polygons, true); } } } GUILayout.EndHorizontal(); List <PrimitiveBrush> orderedTargets = BrushTargets.ToList(); orderedTargets.Sort((x, y) => x.transform.GetSiblingIndex().CompareTo(y.transform.GetSiblingIndex())); if (GUILayout.Button("Set As First")) { for (int i = 0; i < orderedTargets.Count; i++) { // REVERSED PrimitiveBrush thisBrush = orderedTargets[orderedTargets.Count - 1 - i]; Undo.SetTransformParent(thisBrush.transform, thisBrush.transform.parent, "Change Order"); thisBrush.transform.SetAsFirstSibling(); } // Force all the brushes to recalculate their intersections and get ready for rebuilding for (int i = 0; i < orderedTargets.Count; i++) { orderedTargets[i].RecalculateIntersections(); orderedTargets[i].BrushCache.SetUnbuilt(); } } if (GUILayout.Button("Send Earlier")) { for (int i = 0; i < orderedTargets.Count; i++) { PrimitiveBrush thisBrush = orderedTargets[i]; int siblingIndex = thisBrush.transform.GetSiblingIndex(); if (siblingIndex > 0) { Undo.SetTransformParent(thisBrush.transform, thisBrush.transform.parent, "Change Order"); siblingIndex--; thisBrush.transform.SetSiblingIndex(siblingIndex); } } // Force all the brushes to recalculate their intersections and get ready for rebuilding for (int i = 0; i < orderedTargets.Count; i++) { orderedTargets[i].RecalculateIntersections(); orderedTargets[i].BrushCache.SetUnbuilt(); } } if (GUILayout.Button("Send Later")) { for (int i = 0; i < orderedTargets.Count; i++) { // REVERSED PrimitiveBrush thisBrush = orderedTargets[orderedTargets.Count - 1 - i]; int siblingIndex = thisBrush.transform.GetSiblingIndex(); Undo.SetTransformParent(thisBrush.transform, thisBrush.transform.parent, "Change Order"); siblingIndex++; thisBrush.transform.SetSiblingIndex(siblingIndex); } // Force all the brushes to recalculate their intersections and get ready for rebuilding for (int i = 0; i < orderedTargets.Count; i++) { orderedTargets[i].RecalculateIntersections(); orderedTargets[i].BrushCache.SetUnbuilt(); } } if (GUILayout.Button("Set As Last")) { for (int i = 0; i < orderedTargets.Count; i++) { PrimitiveBrush thisBrush = orderedTargets[i]; Undo.SetTransformParent(thisBrush.transform, thisBrush.transform.parent, "Change Order"); thisBrush.transform.SetAsLastSibling(); } // Force all the brushes to recalculate their intersections and get ready for rebuilding for (int i = 0; i < orderedTargets.Count; i++) { orderedTargets[i].RecalculateIntersections(); orderedTargets[i].BrushCache.SetUnbuilt(); } } serializedObject.ApplyModifiedProperties(); // GUILayout.Label("UVs", EditorStyles.boldLabel); // // if (GUILayout.Button("Flip XY")) // { // UVUtility.FlipUVsXY(thisBrush.Polygons); // } // // GUILayout.BeginHorizontal(); // if (GUILayout.Button("Flip X")) // { // UVUtility.FlipUVsX(thisBrush.Polygons); // } // if (GUILayout.Button("Flip Y")) // { // UVUtility.FlipUVsY(thisBrush.Polygons); // } // GUILayout.EndHorizontal(); // // GUILayout.BeginHorizontal(); // if (GUILayout.Button("UVs x 2")) // { // UVUtility.ScaleUVs(thisBrush.Polygons, 2f); // } // if (GUILayout.Button("UVs / 2")) // { // UVUtility.ScaleUVs(thisBrush.Polygons, .5f); // } // GUILayout.EndHorizontal(); // Ensure Edit Mode is on // csgModel.EditMode = true; }
public override void Invalidate(bool polygonsChanged) { base.Invalidate(polygonsChanged); //////////////////////////////////////////////////////////////////// // a little hack to detect the user manually resizing the bounds. // // we use this to automatically add steps for barnaby. // // it's probably good to build a more 'official' way to detect // // user scaling events in compound brushes sometime. // if (m_LastKnownExtents != localBounds.extents) // { // // undo any position movement. // transform.localPosition = m_LastKnownPosition; // // user is trying to scale up. // if (localBounds.extents.y > m_LastKnownExtents.y) // { // numSteps += 1; // m_LastKnownExtents = localBounds.extents; // Invalidate(true); // recusion! <3 // return; // } // // user is trying to scale down. // if (localBounds.extents.y < m_LastKnownExtents.y) // { // numSteps -= 1; // if (numSteps < 1) { numSteps = 1; // } m_LastKnownExtents = localBounds.extents; // Invalidate(true); // recusion! <3 // return; // } // } // //////////////////////////////////////////////////////////////////// // local variables List <Vector3> vertexPositions = new List <Vector3>(); Plane plane; Vector3 rotateStep = new Vector3(); Vector3 vertex = new Vector3(), newVertex = new Vector3(); float adjustment; int innerStart, outerStart, bottomInnerStart, bottomOuterStart; // begin rotateStep.z = angleOfCurve / numSteps; if (counterClockwise) { rotateStep.z *= -1; } // generate the inner curve points. innerStart = vertexPositions.Count; vertex.x = innerRadius; for (int x = 0; x < (numSteps + 1); x++) { if (x == 0) { adjustment = addToFirstStep; } else { adjustment = 0; } newVertex = Quaternion.Euler(rotateStep * x) * vertex; vertexPositions.Add(new Vector3(newVertex.x, vertex.z - adjustment, newVertex.y)); vertex.z += stepHeight; vertexPositions.Add(new Vector3(newVertex.x, vertex.z, newVertex.y)); } // generate the outer curve points. outerStart = vertexPositions.Count; vertex.x = innerRadius + stepWidth; vertex.z = 0; for (int x = 0; x < (numSteps + 1); x++) { if (x == 0) { adjustment = addToFirstStep; } else { adjustment = 0; } newVertex = Quaternion.Euler(rotateStep * x) * vertex; vertexPositions.Add(new Vector3(newVertex.x, vertex.z - adjustment, newVertex.y)); vertex.z += stepHeight; vertexPositions.Add(new Vector3(newVertex.x, vertex.z, newVertex.y)); } // generate the bottom inner curve points. bottomInnerStart = vertexPositions.Count; vertex.x = innerRadius; vertex.z = 0; for (int x = 0; x < (numSteps + 1); x++) { newVertex = Quaternion.Euler(rotateStep * x) * vertex; vertexPositions.Add(new Vector3(newVertex.x, vertex.z - addToFirstStep, newVertex.y)); } // generate the bottom outer curve points. bottomOuterStart = vertexPositions.Count; vertex.x = innerRadius + stepWidth; for (int x = 0; x < (numSteps + 1); x++) { newVertex = Quaternion.Euler(rotateStep * x) * vertex; vertexPositions.Add(new Vector3(newVertex.x, vertex.z - addToFirstStep, newVertex.y)); } // vertex indices to easily flip faces for the counter clockwise mode. int index0 = 0; int index1 = 1; int index2 = 2; int index3 = 3; // flip faces if counter clockwise mode is enabled. if (counterClockwise) { index0 = 2; index1 = 1; index2 = 0; index3 = 3; } // we calculate the bounds of the output csg. Bounds csgBounds = new Bounds(); // iterate through the brushes we received: int brushCount = BrushCount; for (int i = 0; i < brushCount; i++) { // copy our csg information to our child brushes. generatedBrushes[i].Mode = this.Mode; generatedBrushes[i].IsNoCSG = this.IsNoCSG; generatedBrushes[i].IsVisible = this.IsVisible; generatedBrushes[i].HasCollision = this.HasCollision; // retrieve the polygons from the current cube brush. Polygon[] polygons = generatedBrushes[i].GetPolygons(); // +-----------------------------------------------------+ // | Cube Polygons | // +--------+--------+--------+--------+--------+--------+ // | Poly:0 | Poly:1 | Poly:2 | Poly:3 | Poly:4 | Poly:5 | // +-----------------------------------------------------+ // | Back | Left | Right | Front | Bottom | Top | // +--------+--------+--------+--------+--------+--------+ // retrieve the vertices of the top polygon. Vertex[] vertices = polygons[5].Vertices; // step top. vertices[index0].Position = vertexPositions[outerStart + (i * 2) + 2]; vertices[index1].Position = vertexPositions[outerStart + (i * 2) + 1]; vertices[index2].Position = vertexPositions[innerStart + (i * 2) + 1]; vertices[index3].Position = vertexPositions[innerStart + (i * 2) + 2]; // update uv coordinates to prevent distortions using barnaby's genius utilities. vertices[index0].UV = GeometryHelper.GetUVForPosition(polygons[5], vertexPositions[outerStart + (i * 2) + 2]); vertices[index1].UV = GeometryHelper.GetUVForPosition(polygons[5], vertexPositions[outerStart + (i * 2) + 1]); vertices[index2].UV = GeometryHelper.GetUVForPosition(polygons[5], vertexPositions[innerStart + (i * 2) + 1]); vertices[index3].UV = GeometryHelper.GetUVForPosition(polygons[5], vertexPositions[innerStart + (i * 2) + 2]); // retrieve the vertices of the front polygon. vertices = polygons[3].Vertices; // step front. vertices[index0].Position = vertexPositions[outerStart + (i * 2) + 1]; vertices[index1].Position = vertexPositions[bottomOuterStart + i]; vertices[index2].Position = vertexPositions[bottomInnerStart + i]; vertices[index3].Position = vertexPositions[innerStart + (i * 2) + 1]; // calculate a normal using a virtual plane. plane = new Plane(vertices[index1].Position, vertices[index2].Position, vertices[index3].Position); vertices[index0].Normal = plane.normal; vertices[index1].Normal = plane.normal; vertices[index2].Normal = plane.normal; vertices[index3].Normal = plane.normal; // retrieve the vertices of the left polygon. vertices = polygons[1].Vertices; // inner curve. vertices[index0].Position = vertexPositions[bottomInnerStart + i + 1]; vertices[index1].Position = vertexPositions[innerStart + (i * 2) + 2]; vertices[index2].Position = vertexPositions[innerStart + (i * 2) + 1]; vertices[index3].Position = vertexPositions[bottomInnerStart + i]; // calculate a normal using a virtual plane. plane = new Plane(vertices[index1].Position, vertices[index2].Position, vertices[index3].Position); vertices[index0].Normal = plane.normal; vertices[index1].Normal = plane.normal; vertices[index2].Normal = plane.normal; vertices[index3].Normal = plane.normal; // retrieve the vertices of the right polygon. vertices = polygons[2].Vertices; // outer curve. vertices[index0].Position = vertexPositions[outerStart + (i * 2) + 2]; vertices[index1].Position = vertexPositions[bottomOuterStart + i + 1]; vertices[index2].Position = vertexPositions[bottomOuterStart + i]; vertices[index3].Position = vertexPositions[outerStart + (i * 2) + 1]; // calculate a normal using a virtual plane. plane = new Plane(vertices[index1].Position, vertices[index2].Position, vertices[index3].Position); vertices[index0].Normal = plane.normal; vertices[index1].Normal = plane.normal; vertices[index2].Normal = plane.normal; vertices[index3].Normal = plane.normal; // retrieve the vertices of the bottom polygon. vertices = polygons[4].Vertices; // bottom. vertices[index0].Position = vertexPositions[bottomOuterStart + i]; vertices[index1].Position = vertexPositions[bottomOuterStart + i + 1]; vertices[index2].Position = vertexPositions[bottomInnerStart + i + 1]; vertices[index3].Position = vertexPositions[bottomInnerStart + i]; // update uv coordinates to prevent distortions using barnaby's genius utilities. vertices[index0].UV = GeometryHelper.GetUVForPosition(polygons[4], vertexPositions[bottomOuterStart + i]); vertices[index1].UV = GeometryHelper.GetUVForPosition(polygons[4], vertexPositions[bottomOuterStart + i + 1]); vertices[index2].UV = GeometryHelper.GetUVForPosition(polygons[4], vertexPositions[bottomInnerStart + i + 1]); vertices[index3].UV = GeometryHelper.GetUVForPosition(polygons[4], vertexPositions[bottomInnerStart + i]); // retrieve the vertices of the back polygon. vertices = polygons[0].Vertices; // back panel. vertices[index0].Position = vertexPositions[bottomOuterStart + i + 1]; vertices[index1].Position = vertexPositions[outerStart + (i * 2) + 2]; vertices[index2].Position = vertexPositions[innerStart + (i * 2) + 2]; vertices[index3].Position = vertexPositions[bottomInnerStart + i + 1]; // calculate a normal using a virtual plane. plane = new Plane(vertices[index1].Position, vertices[index2].Position, vertices[index3].Position); vertices[index0].Normal = plane.normal; vertices[index1].Normal = plane.normal; vertices[index2].Normal = plane.normal; vertices[index3].Normal = plane.normal; generatedBrushes[i].Invalidate(true); csgBounds.Encapsulate(generatedBrushes[i].GetBounds()); } // apply the generated csg bounds. localBounds = csgBounds; m_LastKnownExtents = localBounds.extents; m_LastKnownPosition = transform.localPosition; }
public void TranslateSelectedVertices(Vector3 worldDelta) { // So we know which polygons need to have their normals recalculated List <Polygon> affectedPolygons = new List <Polygon>(); foreach (PrimitiveBrush brush in targetBrushes) { Polygon[] polygons = brush.GetPolygons(); Vector3 localDelta = brush.transform.InverseTransformDirection(worldDelta); for (int i = 0; i < polygons.Length; i++) { Polygon polygon = polygons[i]; int vertexCount = polygon.Vertices.Length; Vector3[] newPositions = new Vector3[vertexCount]; Vector2[] newUV = new Vector2[vertexCount]; for (int j = 0; j < vertexCount; j++) { newPositions[j] = polygon.Vertices[j].Position; newUV[j] = polygon.Vertices[j].UV; } bool polygonAffected = false; for (int j = 0; j < vertexCount; j++) { Vertex vertex = polygon.Vertices[j]; if (selectedVertices.ContainsKey(vertex)) { Vector3 startPosition = startPositions[vertex]; Vector3 newPosition = vertex.Position + localDelta; Vector3 accumulatedDelta = newPosition - startPosition; if (CurrentSettings.PositionSnappingEnabled) { float snapDistance = CurrentSettings.PositionSnapDistance; // newPosition = targetBrush.transform.TransformPoint(newPosition); accumulatedDelta = MathHelper.RoundVector3(accumulatedDelta, snapDistance); // newPosition = targetBrush.transform.InverseTransformPoint(newPosition); } if (accumulatedDelta != Vector3.zero) { newPosition = startPosition + accumulatedDelta; newPositions[j] = newPosition; newUV[j] = GeometryHelper.GetUVForPosition(polygon, newPosition); polygonAffected = true; } } } if (polygonAffected) { affectedPolygons.Add(polygon); } // Apply all the changes to the polygon for (int j = 0; j < vertexCount; j++) { Vertex vertex = polygon.Vertices[j]; vertex.Position = newPositions[j]; vertex.UV = newUV[j]; } polygon.CalculatePlane(); } } if (affectedPolygons.Count > 0) { for (int i = 0; i < affectedPolygons.Count; i++) { affectedPolygons[i].ResetVertexNormals(); } foreach (PrimitiveBrush brush in targetBrushes) { brush.Invalidate(true); brush.BreakTypeRelation(); } } }
/// <summary> /// Translates the specified vertices by a position delta (local to the brush) and updates the UVs /// </summary> /// <param name="brush">Brush from which the vertices belong.</param> /// <param name="specifiedVertices">Specified vertices to be translated.</param> /// <param name="localDelta">Local positional delta.</param> public static void TranslateSpecifiedVertices(Brush brush, List <Vertex> specifiedVertices, Vector3 localDelta) { Polygon.Vector3ComparerEpsilon positionComparer = new Polygon.Vector3ComparerEpsilon(); // Cache the positions as the position of vertices will change while in the for loop List <Vector3> specifiedPositions = specifiedVertices.Select(item => item.Position).ToList(); // So we know which polygons need to have their normals recalculated List <Polygon> affectedPolygons = new List <Polygon>(); Polygon[] polygons = brush.GetPolygons(); for (int i = 0; i < polygons.Length; i++) { Polygon polygon = polygons[i]; int vertexCount = polygon.Vertices.Length; Vector3[] newPositions = new Vector3[vertexCount]; Vector2[] newUV = new Vector2[vertexCount]; for (int j = 0; j < vertexCount; j++) { newPositions[j] = polygon.Vertices[j].Position; newUV[j] = polygon.Vertices[j].UV; } bool polygonAffected = false; for (int j = 0; j < vertexCount; j++) { Vertex vertex = polygon.Vertices[j]; if (specifiedPositions.Contains(vertex.Position, positionComparer)) { Vector3 newPosition = vertex.Position + localDelta; newPositions[j] = newPosition; newUV[j] = GeometryHelper.GetUVForPosition(polygon, newPosition); polygonAffected = true; } } if (polygonAffected) { affectedPolygons.Add(polygon); } // Apply all the changes to the polygon for (int j = 0; j < vertexCount; j++) { Vertex vertex = polygon.Vertices[j]; vertex.Position = newPositions[j]; vertex.UV = newUV[j]; } polygon.CalculatePlane(); } if (affectedPolygons.Count > 0) { for (int i = 0; i < affectedPolygons.Count; i++) { affectedPolygons[i].ResetVertexNormals(); } } }
/// <summary> /// Tells the brush it has changed /// </summary> /// <param name="polygonsChanged">If set to <c>true</c> polygons will be recached.</param> public override void Invalidate(bool polygonsChanged) { if (!gameObject.activeInHierarchy) { return; } // Make sure there is a mesh filter on this object MeshFilter meshFilter = gameObject.AddOrGetComponent <MeshFilter>(); MeshRenderer meshRenderer = gameObject.AddOrGetComponent <MeshRenderer>(); // Used to use mesh colliders for ray collision, but not any more so clean them up MeshCollider[] meshColliders = GetComponents <MeshCollider>(); if (meshColliders.Length > 0) { for (int i = 0; i < meshColliders.Length; i++) { DestroyImmediate(meshColliders[i]); } } bool requireRegen = false; // If the cached ID hasn't been set or we mismatch if (cachedInstanceID == 0 || gameObject.GetInstanceID() != cachedInstanceID) { requireRegen = true; cachedInstanceID = gameObject.GetInstanceID(); } Mesh renderMesh = meshFilter.sharedMesh; if (requireRegen) { renderMesh = new Mesh(); } if (polygons != null) { List <int> polygonIndices; BrushFactory.GenerateMeshFromPolygons(polygons, ref renderMesh, out polygonIndices); } if (mode == CSGMode.Subtract) { MeshHelper.Invert(ref renderMesh); } // Displace the triangles for display along the normals very slightly (this is so we can overlay built // geometry with semi-transparent geometry and avoid depth fighting) MeshHelper.Displace(ref renderMesh, 0.001f); meshFilter.sharedMesh = renderMesh; meshRenderer.receiveShadows = false; meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; meshFilter.hideFlags = HideFlags.NotEditable; // | HideFlags.HideInInspector; meshRenderer.hideFlags = HideFlags.NotEditable; // | HideFlags.HideInInspector; #if UNITY_EDITOR Material material; if (IsNoCSG) { material = UnityEditor.AssetDatabase.LoadMainAssetAtPath(CSGModel.GetSabreCSGPath() + "Materials/NoCSG.mat") as Material; } else { material = UnityEditor.AssetDatabase.LoadMainAssetAtPath(CSGModel.GetSabreCSGPath() + "Materials/" + this.mode.ToString() + ".mat") as Material; } meshRenderer.sharedMaterial = material; #endif isBrushConvex = GeometryHelper.IsBrushConvex(polygons); if (polygonsChanged) { RecalculateBrushCache(); } UpdateVisibility(); objectVersionSerialized++; objectVersionUnserialized = objectVersionSerialized; }
internal List <Polygon> ProvideSubtractChunks(List <BrushChunk> subtractChunks) { List <Polygon> damagedPolygons = new List <Polygon>(); List <Polygon> addedPolygons = new List <Polygon>(); // If this chunk knows about any split planes // if(splitPlanes.Count > 0) { // For each of the subtract chunks int subtractChunkCount = subtractChunks.Count; for (int chunkIndex = 0; chunkIndex < subtractChunkCount; chunkIndex++) { int polygonCount = subtractChunks[chunkIndex].Polygons.Count; // For each of the polygons within those subtract chunks for (int j = 0; j < polygonCount; j++) { Polygon polygon = subtractChunks[chunkIndex].Polygons[j]; // Disregard any polygons that can't be displayed as final geometry if (polygon.ExcludeFromFinal) { continue; } Plane polygonPlane = polygon.CachedPlaneTest; // Determine if any of the split planes this chunk has been split against match any of those // subtraction polygons bool anyFound = false; int polygonsCount = polygons.Count; Plane splitPlane; for (int i = 0; i < polygonsCount; i++) { if (!polygons[i].ExcludeFromFinal) { continue; } splitPlane = polygons[i].CachedPlaneTest; if (MathHelper.PlaneEqualsLooserWithFlip(polygonPlane, splitPlane)) { anyFound = true; break; } } bool added = false; if (anyFound) { // TODO: Is GetCenterPoint expensive? Vector3 target = polygon.GetCenterPoint(); // If this brush chunk contains the subtraction polygon // TODO: This is a heftly call a lot of the time, so see about optimising if (GeometryHelper.PolyhedronContainsPointEpsilon3(this.polygons, target)) { added = true; // Duplicate the subtraction polygon polygon = polygon.DeepCopy(); // Flip it, so that it can form outer geometry polygon.Flip(); // // for (int i = 0; i < this.polygons.Count; i++) { if (!this.polygons[i].ExcludeFromFinal && MathHelper.PlaneEqualsLooser(this.polygons[i].Plane, polygon.Plane)) { if (!addedPolygons.Contains(this.polygons[i])) { //Debug.LogWarning("Removing duplicate from chunk " + this.uniqueID + " subtraction chunk " + subtractChunks[chunkIndex].UniqueID); polygons[i].ExcludeFromFinal = true; } } } // Add it to this brush chunk polygons.Add(polygon); addedPolygons.Add(polygon); } } if (!added) { damagedPolygons.Add(polygon); } } } } return(damagedPolygons); }
protected static List <Brush> CalculateIntersectingBrushes(Brush sourceBrush, List <Brush> brushes, bool isCollisionPass) { // If the brush is not CSG it can't intersect any brushes! if (sourceBrush.IsNoCSG || sourceBrush.Mode == CSGMode.Volume) { return(new List <Brush>()); } // Return empty lists if the pass is not relevant if (isCollisionPass) { if (!sourceBrush.hasCollision) { return(new List <Brush>()); } } else { if (!sourceBrush.isVisible) { return(new List <Brush>()); } } BrushCache sourceCache = sourceBrush.BrushCache; List <Brush> intersectingBrushes = new List <Brush>(); Bounds targetBounds = sourceCache.Bounds; Polygon[] targetPolygons = sourceCache.Polygons; // Find the index of this brush int thisIndex = -1; for (int i = 0; i < brushes.Count; i++) { if (brushes[i] != null) { BrushCache brushCache = brushes[i].BrushCache; if (brushCache == sourceCache) { thisIndex = i; break; } } } // Go through the brushes before this one for (int i = thisIndex - 1; i >= 0; i--) { if (!Brush.IsInvalidForBuild(brushes[i])) { // Skip any brushes not suitable for the pass if (brushes[i].isNoCSG || brushes[i].mode == CSGMode.Volume) { // NoCSG and volume brushes skip the CSG calcs continue; } else if (isCollisionPass && !brushes[i].HasCollision) { continue; } else if (!isCollisionPass && !brushes[i].IsVisible) { continue; } BrushCache brushCache = brushes[i].BrushCache; if (brushCache.Bounds.IntersectsApproximate(targetBounds)) { if (GeometryHelper.PolyhedraIntersect(brushCache.Polygons, targetPolygons)) { intersectingBrushes.Add(brushes[i]); // If the brush is contained entirely by a previous subtraction then it's impossible // to intersect with any brushes before that subtraction if (brushCache.Mode == CSGMode.Subtract) { if (GeometryHelper.PolyhedronContainsPolyhedron(brushCache.Polygons, targetPolygons)) { break; } } } } } } intersectingBrushes.Reverse(); List <BrushCache> activeSubtractions = new List <BrushCache>(); // Go through the brushes after this one for (int i = thisIndex + 1; i < brushes.Count; i++) { if (!Brush.IsInvalidForBuild(brushes[i])) { // Skip any brushes not suitable for the pass if (brushes[i].isNoCSG || brushes[i].mode == CSGMode.Volume) { // NoCSG and volume brushes skip the CSG calcs continue; } else if (isCollisionPass && !brushes[i].HasCollision) { // Collision pass and this brush has no collision so skip continue; } else if (!isCollisionPass && !brushes[i].IsVisible) { // Visual pass and this brush isn't visible so skip continue; } BrushCache brushCache = brushes[i].BrushCache; if (brushCache.Bounds.IntersectsApproximate(targetBounds)) { if (GeometryHelper.PolyhedraIntersect(brushCache.Polygons, targetPolygons)) { bool containedByPreviousSubtraction = false; for (int j = 0; j < activeSubtractions.Count; j++) { BrushCache subtractionCache = activeSubtractions[j]; if (subtractionCache.Bounds.IntersectsApproximate(brushCache.Bounds)) { if (GeometryHelper.PolyhedronContainsPolyhedron(subtractionCache.Polygons, brushCache.Polygons)) { containedByPreviousSubtraction = true; break; } } } if (!containedByPreviousSubtraction) { intersectingBrushes.Add(brushes[i]); if (brushCache.Mode == CSGMode.Subtract) { activeSubtractions.Add(brushCache); } } } } } } return(intersectingBrushes); }
public override void PerformWeld() { List <List <Vertex> > groupedVertices = new List <List <Vertex> >(); VertexComparerTolerance comparer = new VertexComparerTolerance(tolerance); // Group the selected vertices into clusters for (int sourceVertexIndex = 0; sourceVertexIndex < sourceVertices.Count; sourceVertexIndex++) { Vertex sourceVertex = sourceVertices[sourceVertexIndex]; bool added = false; for (int groupIndex = 0; groupIndex < groupedVertices.Count; groupIndex++) { if (groupedVertices[groupIndex].Contains(sourceVertex, comparer)) { groupedVertices[groupIndex].Add(sourceVertex); added = true; break; } } if (!added) { groupedVertices.Add(new List <Vertex>() { sourceVertex }); } } for (int groupIndex = 0; groupIndex < groupedVertices.Count; groupIndex++) { List <Vertex> vertices = groupedVertices[groupIndex]; // Ignoring any groups that only contain one vertex if (vertices.Count > 1) { // New position for the vertices is their center Vector3 newPosition = Vector3.zero; for (int vertexIndex = 0; vertexIndex < vertices.Count; vertexIndex++) { newPosition += vertices[vertexIndex].Position; } newPosition /= vertices.Count; // Update all the selected vertices UVs for (int vertexIndex = 0; vertexIndex < vertices.Count; vertexIndex++) { Polygon polygon = vertexPolygonMappings[vertices[vertexIndex]]; vertices[vertexIndex].UV = GeometryHelper.GetUVForPosition(polygon, newPosition); } // Update all the selected vertices to their new position for (int vertexIndex = 0; vertexIndex < vertices.Count; vertexIndex++) { vertices[vertexIndex].Position = newPosition; } } } }
void OnMouseAction3D(SceneView sceneView, Event e) { if (primaryTargetBrush == null || CameraPanInProgress || e.button != 0) { return; } Vector2 mousePosition = e.mousePosition; mousePosition = EditorHelper.ConvertMousePointPosition(mousePosition); Ray ray = Camera.current.ScreenPointToRay(mousePosition); float bestDistance = float.PositiveInfinity; Polygon bestPolygon = null; float testDistance; foreach (Brush brush in targetBrushes) { List <Polygon> polygons = brush.GenerateTransformedPolygons().ToList(); Polygon testPolygon = GeometryHelper.RaycastPolygons(polygons, ray, out testDistance, 0.1f); if (testPolygon != null && testDistance < bestDistance) { bestDistance = testDistance; bestPolygon = testPolygon; } } if (bestPolygon != null) { // VisualDebug.ClearAll(); // VisualDebug.AddPolygon(hitPolygon, Color.red); Vector3 hitPoint = ray.GetPoint(bestDistance); if (e.type == EventType.MouseDown || e.type == EventType.MouseMove) { if (e.type == EventType.MouseMove && planeEstablished) { return; } points[0] = hitPoint; displayPoint = true; if (CurrentSettings.PositionSnappingEnabled) { float snapDistance = CurrentSettings.PositionSnapDistance; points[0] = MathHelper.RoundVector3(points[0], snapDistance); } points[1] = points[0]; points[2] = points[0]; isFlipped = false; } else { points[1] = hitPoint; if (CurrentSettings.PositionSnappingEnabled) { float snapDistance = CurrentSettings.PositionSnapDistance; points[1] = MathHelper.RoundVector3(points[1], snapDistance); } points[2] = points[0] - bestPolygon.Plane.normal; if (e.type == EventType.MouseUp) { planeEstablished = true; if (points[1] == points[0]) { ResetTool(); } } } SceneView.RepaintAll(); } }
private void CreateBrush(List <Vector3> positions) { Polygon sourcePolygon = PolygonFactory.ConstructPolygon(positions, true); // Early out if it wasn't possible to create the polygon if (sourcePolygon == null) { return; } if (activePolygon != null) { for (int i = 0; i < sourcePolygon.Vertices.Length; i++) { Vector2 newUV = GeometryHelper.GetUVForPosition(activePolygon, sourcePolygon.Vertices[i].Position); sourcePolygon.Vertices[i].UV = newUV; } } Vector3 planeNormal = GetActivePlane().normal; // Debug.Log(Vector3.Dot(sourcePolygon.Plane.normal, planeNormal)); // Flip the polygon if the winding order is wrong if (Vector3.Dot(sourcePolygon.Plane.normal, planeNormal) < 0) { sourcePolygon.Flip(); // Need to flip the UVs across the U (X) direction for (int i = 0; i < sourcePolygon.Vertices.Length; i++) { Vector2 uv = sourcePolygon.Vertices[i].UV; uv.x = 1 - uv.x; sourcePolygon.Vertices[i].UV = uv; } } float extrusionDistance = 1; Vector3 positionOffset = Vector3.zero; if (selectingHeight) { extrusionDistance = prismHeight; } else { if (activePolygon != null && activeBrush != null) { extrusionDistance = activeBrush.CalculateExtentsInAxis(planeNormal); } else { Brush lastSelectedBrush = csgModel.LastSelectedBrush; if (lastSelectedBrush != null) { Bounds lastSelectedBrushBounds = lastSelectedBrush.GetBoundsTransformed(); for (int i = 0; i < 3; i++) { if (!planeNormal[i].EqualsWithEpsilon(0)) { if (lastSelectedBrushBounds.size[i] != 0) { extrusionDistance = lastSelectedBrushBounds.size[i]; if (planeNormal[i] > 0) { positionOffset[i] = lastSelectedBrushBounds.center[i] - lastSelectedBrushBounds.extents[i]; } else { positionOffset[i] = lastSelectedBrushBounds.center[i] + lastSelectedBrushBounds.extents[i]; } } } } } } // Subtractions should go through if (csgMode == CSGMode.Subtract) { sourcePolygon.Flip(); } } Quaternion rotation; Polygon[] polygons; SurfaceUtility.ExtrudePolygon(sourcePolygon, extrusionDistance, out polygons, out rotation); GameObject newObject = csgModel.CreateCustomBrush(polygons); PrimitiveBrush newBrush = newObject.GetComponent <PrimitiveBrush>(); newObject.transform.rotation = rotation; newObject.transform.position += positionOffset; if (activePolygon != null && activePolygon.Material != csgModel.GetDefaultMaterial()) { for (int i = 0; i < polygons.Length; i++) { polygons[i].Material = activePolygon.Material; } } // Finally give the new brush the other set of polygons newBrush.SetPolygons(polygons, true); newBrush.Mode = csgMode; newBrush.ResetPivot(); // Use this brush as the basis for drawing the next brush csgModel.SetLastSelectedBrush(newBrush); Undo.RegisterCreatedObjectUndo(newObject, "Draw Brush"); }
private static void SplitAndRemove(LinkedList <BrushChunk> brushChunks, BrushCache removee, int[] polygonsRemoved) { Polygon[] polygons = removee.Polygons; Plane[] splitPlanes = removee.SplitPlanes; for (int polygonIndex = 0; polygonIndex < polygons.Length; polygonIndex++) { Plane splitPlane = splitPlanes[polygonIndex]; for (LinkedListNode <BrushChunk> current = brushChunks.First; current != null;) { #if !NO_EARLYOUT if (!current.Value.GetBounds().IntersectsApproximate(removee.Bounds)) { current = current.Next; continue; } #endif BrushChunk chunkIn; BrushChunk chunkOut; if (current.Value.SplitByPlane(splitPlane, out chunkIn, out chunkOut)) { // TODO: If chunkIn is fully inside polygons, delete it current.Value = chunkOut; if (!GeometryHelper.PolyhedronContainsPolyhedron(polygons, chunkIn.Polygons)) { current = brushChunks.AddAfter(current, chunkIn); } else { for (int i = 0; i < chunkIn.Polygons.Count; i++) { if (chunkIn.Polygons[i].UniqueIndex != -1) { int relativeIndex = chunkIn.Polygons[i].UniqueIndex - firstPolygonUID; MarkPolygonRemoved(relativeIndex, polygonsRemoved); } } } // Next iteration current = current.Next; } else { LinkedListNode <BrushChunk> next = current.Next; BrushChunk chunk = current.Value; if (GeometryHelper.PolyhedronContainsPolyhedron(polygons, chunk.Polygons)) { for (int i = 0; i < chunk.Polygons.Count; i++) { if (chunk.Polygons[i].UniqueIndex != -1) { int relativeIndex = chunk.Polygons[i].UniqueIndex - firstPolygonUID; MarkPolygonRemoved(relativeIndex, polygonsRemoved); } } brushChunks.Remove(current); } // Next iteration current = next; } } } }
public static void DisplacePolygons(Polygon[] polygons, float distance) { // Used for determining if two vertices are the same Polygon.VertexComparerEpsilon vertexComparer = new Polygon.VertexComparerEpsilon(); // Used for determining if two positions or normals are the same Polygon.Vector3ComparerEpsilon vectorComparer = new Polygon.Vector3ComparerEpsilon(); // Group overlapping positions and also track their normals List <List <Vertex> > groupedVertices = new List <List <Vertex> >(); List <List <Vector3> > groupedNormals = new List <List <Vector3> >(); // Maps back from a vertex to the polygon it came from, used for UV calculation Dictionary <Vertex, Polygon> vertexPolygonMappings = new Dictionary <Vertex, Polygon>(); for (int polygonIndex = 0; polygonIndex < polygons.Length; polygonIndex++) { Vertex[] vertices = polygons[polygonIndex].Vertices; // Group the selected vertices into clusters for (int vertexIndex = 0; vertexIndex < vertices.Length; vertexIndex++) { Vertex sourceVertex = vertices[vertexIndex]; vertexPolygonMappings[sourceVertex] = polygons[polygonIndex]; bool added = false; for (int groupIndex = 0; groupIndex < groupedVertices.Count; groupIndex++) { if (groupedVertices[groupIndex].Contains(sourceVertex, vertexComparer)) { groupedVertices[groupIndex].Add(sourceVertex); // Add the normal of the polygon if it hasn't already been added (this prevents issues with two polygons that are coplanar) if (!groupedNormals[groupIndex].Contains(polygons[polygonIndex].Plane.normal, vectorComparer)) { groupedNormals[groupIndex].Add(polygons[polygonIndex].Plane.normal); } added = true; break; } } if (!added) { groupedVertices.Add(new List <Vertex>() { sourceVertex }); groupedNormals.Add(new List <Vector3>() { polygons[polygonIndex].Plane.normal }); } } } List <List <Vector3> > groupedPositions = new List <List <Vector3> >(); List <List <Vector2> > groupedUV = new List <List <Vector2> >(); // Calculate the new positions and UVs, but don't assign them as they must be calculated in one go for (int i = 0; i < groupedVertices.Count; i++) { groupedPositions.Add(new List <Vector3>()); groupedUV.Add(new List <Vector2>()); for (int j = 0; j < groupedVertices[i].Count; j++) { Vector3 position = groupedVertices[i][j].Position; for (int k = 0; k < groupedNormals[i].Count; k++) { position += groupedNormals[i][k] * distance; } Polygon primaryPolygon = vertexPolygonMappings[groupedVertices[i][j]]; Vector2 uv = GeometryHelper.GetUVForPosition(primaryPolygon, position); groupedPositions[i].Add(position); groupedUV[i].Add(uv); } } // Apply the new positions and UVs now that they've all been calculated for (int i = 0; i < groupedVertices.Count; i++) { for (int j = 0; j < groupedVertices[i].Count; j++) { Vertex vertex = groupedVertices[i][j]; vertex.Position = groupedPositions[i][j]; vertex.UV = groupedUV[i][j]; } } // Polygon planes have moved, so recalculate them for (int polygonIndex = 0; polygonIndex < polygons.Length; polygonIndex++) { polygons[polygonIndex].CalculatePlane(); } }
public override void OnInspectorGUI() { float drawableWidth = EditorGUIUtility.currentViewWidth; drawableWidth -= 42; // Take some off for scroll bars and padding PrimitiveBrushType[] selectedTypes = BrushTargets.Select(item => ((PrimitiveBrush)item).BrushType).ToArray(); PrimitiveBrushType?activeType = (selectedTypes.Length == 1) ? (PrimitiveBrushType?)selectedTypes[0] : null; using (new NamedVerticalScope("Type")) { GUILayout.BeginHorizontal(); float areaWidth = drawableWidth - 18; int buttonWidth = Mathf.RoundToInt(areaWidth / 5f); int stretchButtonWidth = Mathf.RoundToInt(areaWidth - buttonWidth * 4); // To ensure a justified alignment one button must be stretched slightly int buttonHeight = 50; GUIStyle brushButtonStyle = new GUIStyle(GUI.skin.button); brushButtonStyle.imagePosition = ImagePosition.ImageAbove; brushButtonStyle.fontSize = 10; GUIStyle labelStyle = new GUIStyle(GUI.skin.label); labelStyle.alignment = TextAnchor.LowerCenter; labelStyle.fontSize = brushButtonStyle.fontSize; bool shortMode = (areaWidth < 260); // Whether certain words need to be abbreviated to fit in the box DrawBrushButton(PrimitiveBrushType.Cube, activeType, brushButtonStyle, labelStyle, buttonWidth, buttonHeight, shortMode); DrawBrushButton(PrimitiveBrushType.Prism, activeType, brushButtonStyle, labelStyle, buttonWidth, buttonHeight, shortMode); DrawBrushButton(PrimitiveBrushType.Cylinder, activeType, brushButtonStyle, labelStyle, stretchButtonWidth, buttonHeight, shortMode); DrawBrushButton(PrimitiveBrushType.Sphere, activeType, brushButtonStyle, labelStyle, buttonWidth, buttonHeight, shortMode); DrawBrushButton(PrimitiveBrushType.IcoSphere, activeType, brushButtonStyle, labelStyle, buttonWidth, buttonHeight, shortMode); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); DrawBrushButton(PrimitiveBrushType.Cone, activeType, brushButtonStyle, labelStyle, buttonWidth, buttonHeight, shortMode); GUI.enabled = true; // Reset GUI enabled so that the next items aren't disabled GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); if (activeType.HasValue) { GUILayout.Label("Active: " + selectedTypes[0]); } else { GUILayout.Label("Active: Mixed"); } if (activeType.HasValue) { EditorGUIUtility.labelWidth = 60; EditorGUIUtility.fieldWidth = 50; EditorGUI.BeginChangeCheck(); if (activeType.Value == PrimitiveBrushType.Prism) { EditorGUILayout.PropertyField(prismSideCountProp, new GUIContent("Sides")); } else if (activeType.Value == PrimitiveBrushType.Cylinder) { EditorGUILayout.PropertyField(cylinderSideCountProp, new GUIContent("Sides")); } else if (activeType.Value == PrimitiveBrushType.Sphere) { EditorGUILayout.PropertyField(sphereSideCountProp, new GUIContent("Sides")); } else if (activeType.Value == PrimitiveBrushType.IcoSphere) { EditorGUILayout.PropertyField(icoSphereIterationCountProp, new GUIContent("Iterations")); } else if (activeType.Value == PrimitiveBrushType.Cone) { EditorGUILayout.PropertyField(coneSideCountProp, new GUIContent("Sides")); } if (EditorGUI.EndChangeCheck()) { // One of the properties has changed serializedObject.ApplyModifiedProperties(); ResetPolygonsKeepScale(); } } GUILayout.EndHorizontal(); } using (new NamedVerticalScope("Size")) { if (GUILayout.Button(new GUIContent("Reset Bounds", "Resets the bounds of the brush to [2,2,2]"))) { ResetBounds(); } GUILayout.BeginHorizontal(); GUI.SetNextControlName("rescaleTextbox"); scaleString = EditorGUILayout.TextField(scaleString); bool keyboardEnter = Event.current.isKey && Event.current.keyCode == KeyCode.Return && Event.current.type == EventType.KeyUp && GUI.GetNameOfFocusedControl() == "rescaleTextbox"; if (GUILayout.Button("Scale", GUILayout.MaxWidth(drawableWidth / 3f)) || keyboardEnter) { // Try to parse a Vector3 scale from the input string Vector3 scaleVector3; if (StringHelper.TryParseScale(scaleString, out scaleVector3)) { // None of the scale components can be zero if (scaleVector3.x != 0 && scaleVector3.y != 0 && scaleVector3.z != 0) { // Rescale all the brushes Undo.RecordObjects(targets, "Scale Polygons"); foreach (var thisBrush in targets) { BrushUtility.Scale((PrimitiveBrush)thisBrush, scaleVector3); } } } } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); GUI.SetNextControlName("resizeTextbox"); resizeString = EditorGUILayout.TextField(resizeString); keyboardEnter = Event.current.isKey && Event.current.keyCode == KeyCode.Return && Event.current.type == EventType.KeyUp && GUI.GetNameOfFocusedControl() == "resizeTextbox"; if (GUILayout.Button("Resize", GUILayout.MaxWidth(drawableWidth / 3f)) || keyboardEnter) { // Try to parse a Vector3 scale from the input string Vector3 resizeVector3; if (StringHelper.TryParseScale(resizeString, out resizeVector3)) { // None of the size components can be zero if (resizeVector3.x != 0 && resizeVector3.y != 0 && resizeVector3.z != 0) { // Rescale all the brushes so that the local bounds is the same size as the resize vector Undo.RecordObjects(targets, "Resize Polygons"); PrimitiveBrush[] brushes = BrushTargets.Cast <PrimitiveBrush>().ToArray(); foreach (PrimitiveBrush brush in brushes) { BrushUtility.Resize(brush, resizeVector3); } } } } GUILayout.EndHorizontal(); } using (new NamedVerticalScope("Rotation")) { GUILayout.Label("Align up direction", EditorStyles.boldLabel); GUILayout.BeginHorizontal(); if (GUILayout.Button("X")) { AlignUpToAxis(new Vector3(1, 0, 0), false); } if (GUILayout.Button("Y")) { AlignUpToAxis(new Vector3(0, 1, 0), false); } if (GUILayout.Button("Z")) { AlignUpToAxis(new Vector3(0, 0, 1), false); } GUILayout.EndHorizontal(); GUILayout.Label("Align up direction (keep positions)", EditorStyles.boldLabel); GUILayout.BeginHorizontal(); if (GUILayout.Button("X")) { AlignUpToAxis(new Vector3(1, 0, 0), true); } if (GUILayout.Button("Y")) { AlignUpToAxis(new Vector3(0, 1, 0), true); } if (GUILayout.Button("Z")) { AlignUpToAxis(new Vector3(0, 0, 1), true); } GUILayout.EndHorizontal(); } using (new NamedVerticalScope("Misc")) { // Import Row GUILayout.BeginHorizontal(); sourceMesh = EditorGUILayout.ObjectField(sourceMesh, typeof(Mesh), false) as Mesh; if (GUILayout.Button("Import", GUILayout.MaxWidth(drawableWidth / 3f))) { if (sourceMesh != null) { Undo.RecordObjects(targets, "Import Polygons From Mesh"); Polygon[] polygons = BrushFactory.GeneratePolygonsFromMesh(sourceMesh).ToArray(); bool convex = GeometryHelper.IsBrushConvex(polygons); if (!convex) { Debug.LogError("Concavities detected in imported mesh. This may result in issues during CSG, please change the source geometry so that it is convex"); } foreach (var thisBrush in targets) { ((PrimitiveBrush)thisBrush).SetPolygons(polygons, true); } } } GUILayout.EndHorizontal(); // Shell Row GUILayout.BeginHorizontal(); if (shellDistance == 0) { shellDistance = CurrentSettings.PositionSnapDistance; } shellDistance = EditorGUILayout.FloatField("Distance", shellDistance); if (GUILayout.Button("Shell", GUILayout.MaxWidth(drawableWidth / 3f))) { List <GameObject> newSelection = new List <GameObject>(); foreach (var thisBrush in targets) { GameObject newObject = ((PrimitiveBrush)thisBrush).Duplicate(); Polygon[] polygons = newObject.GetComponent <PrimitiveBrush>().GetPolygons(); VertexUtility.DisplacePolygons(polygons, -shellDistance); Bounds newBounds = newObject.GetComponent <PrimitiveBrush>().GetBounds(); // Verify the new geometry if (GeometryHelper.IsBrushConvex(polygons) && newBounds.GetSmallestExtent() > 0) { Undo.RegisterCreatedObjectUndo(newObject, "Shell"); newSelection.Add(newObject); } else { // Produced a concave brush, delete it and pretend nothing happened GameObject.DestroyImmediate(newObject); Debug.LogWarning("Could not shell " + thisBrush.name + " as shelled geometry would not be valid. Try lowering the shell distance and attempt Shell again."); } } if (newSelection.Count > 0) { Selection.objects = newSelection.ToArray(); } } GUILayout.EndHorizontal(); // Split Intersecting Row if (GUILayout.Button("Split Intersecting Brushes")) { // Chop up the intersecting brushes by the brush planes, ideally into as few new brushes as possible PrimitiveBrush[] brushes = BrushTargets.Cast <PrimitiveBrush>().ToArray(); BrushUtility.SplitIntersecting(brushes); } // BrushOrder brushOrder = BrushTarget.GetBrushOrder(); // string positionString = string.Join(",", brushOrder.Position.Select(item => item.ToString()).ToArray()); // GUILayout.Label(positionString, EditorStyles.boldLabel); //List<BrushCache> intersections = ((PrimitiveBrush)BrushTarget).BrushCache.IntersectingVisualBrushCaches; //GUILayout.Label("Intersecting brushes " + intersections.Count, EditorStyles.boldLabel); //for (int i = 0; i < intersections.Count; i++) //{ // GUILayout.Label(intersections[i].Mode.ToString(), EditorStyles.boldLabel); //} } base.OnInspectorGUI(); }
public void TranslateSelectedVertices(Vector3 worldDelta) { foreach (PrimitiveBrush brush in targetBrushes) { bool anyAffected = false; Polygon[] polygons = brush.GetPolygons(); Vector3 localDelta = brush.transform.InverseTransformDirection(worldDelta); for (int i = 0; i < polygons.Length; i++) { Polygon polygon = polygons[i]; polygon.CalculatePlane(); Vector3 previousPlaneNormal = polygons[i].Plane.normal; int vertexCount = polygon.Vertices.Length; Vector3[] newPositions = new Vector3[vertexCount]; Vector2[] newUV = new Vector2[vertexCount]; for (int j = 0; j < vertexCount; j++) { newPositions[j] = polygon.Vertices[j].Position; newUV[j] = polygon.Vertices[j].UV; } bool polygonAffected = false; for (int j = 0; j < vertexCount; j++) { Vertex vertex = polygon.Vertices[j]; if (selectedVertices.ContainsKey(vertex)) { Vector3 startPosition = startPositions[vertex]; Vector3 newPosition = vertex.Position + localDelta; Vector3 accumulatedDelta = newPosition - startPosition; if (CurrentSettings.PositionSnappingEnabled) { float snapDistance = CurrentSettings.PositionSnapDistance; // newPosition = targetBrush.transform.TransformPoint(newPosition); accumulatedDelta = MathHelper.RoundVector3(accumulatedDelta, snapDistance); // newPosition = targetBrush.transform.InverseTransformPoint(newPosition); } if (accumulatedDelta != Vector3.zero) { newPosition = startPosition + accumulatedDelta; newPositions[j] = newPosition; newUV[j] = GeometryHelper.GetUVForPosition(polygon, newPosition); polygonAffected = true; anyAffected = true; } } } // Apply all the changes to the polygon for (int j = 0; j < vertexCount; j++) { Vertex vertex = polygon.Vertices[j]; vertex.Position = newPositions[j]; vertex.UV = newUV[j]; } if (polygonAffected) { // Polygon geometry has changed, inform the polygon that it needs to recalculate its cached plane polygons[i].CalculatePlane(); Vector3 newPlaneNormal = polygons[i].Plane.normal; // Find the rotation from the original polygon plane to the new polygon plane Quaternion normalRotation = Quaternion.FromToRotation(previousPlaneNormal, newPlaneNormal); // Update the affected normals so they are rotated by the rotational difference of the polygon from translation for (int j = 0; j < vertexCount; j++) { Vertex vertex = polygon.Vertices[j]; vertex.Normal = normalRotation * vertex.Normal; } } } if (anyAffected) // If any polygons have changed { // Mark the polygons and brush as having changed brush.Invalidate(true); // Assume that the brush no longer resembles it's base shape, this has false positives but that's not a big issue brush.BreakTypeRelation(); } } }