/// <summary> /// Reset the polygons to those specified in the brush type. For example if the brush type is a cube, the polygons are reset to a cube. /// </summary> public void ResetPolygons() { if (brushType == PrimitiveBrushType.Cube) { polygons = BrushFactory.GenerateCube(); } else if (brushType == PrimitiveBrushType.Cylinder) { if (cylinderSideCount < 3) { cylinderSideCount = 3; } polygons = BrushFactory.GenerateCylinder(cylinderSideCount); } else if (brushType == PrimitiveBrushType.Sphere) { if (sphereSideCount < 3) { sphereSideCount = 3; } // Lateral only goes halfway around the sphere (180 deg), longitudinal goes all the way (360 deg) polygons = BrushFactory.GeneratePolarSphere(sphereSideCount, sphereSideCount * 2); } else if (brushType == PrimitiveBrushType.IcoSphere) { if (icoSphereIterationCount < 0) { icoSphereIterationCount = 0; } else if (icoSphereIterationCount > 2) { icoSphereIterationCount = 2; } polygons = BrushFactory.GenerateIcoSphere(icoSphereIterationCount); } else if (brushType == PrimitiveBrushType.Prism) { if (prismSideCount < 3) { prismSideCount = 3; } polygons = BrushFactory.GeneratePrism(prismSideCount); } else if (brushType == Sabresaurus.SabreCSG.PrimitiveBrushType.Custom) { // Do nothing Debug.LogError("PrimitiveBrushType.Custom is not a valid type for new brush creation"); } else { throw new NotImplementedException(); } }
/// <summary> /// Rebuilds the volume, creating or deleting the volume component and applying new settings. /// </summary> internal void RebuildVolume() { // volumes can only be primitive brushes at the moment. if (GetType() != typeof(PrimitiveBrush)) { return; } PrimitiveBrush self = (PrimitiveBrush)this; // remove volumes from brushes that are no longer volumes: if (Mode != CSGMode.Volume && Volume != null) { // set volume handle to null. Volume = null; // delete any built volume. Transform volume1 = transform.Find(Constants.GameObjectVolumeComponentIdentifier); if (volume1 != null) { GameObject.DestroyImmediate(volume1.gameObject); } } // generate all of the volume brushes: if (Mode == CSGMode.Volume && Volume != null) { // remove any existing built volume: Transform volume2 = transform.Find(Constants.GameObjectVolumeComponentIdentifier); if (volume2 != null) { GameObject.DestroyImmediate(volume2.gameObject); } // create the game object with convex mesh collider: Mesh mesh = new Mesh(); BrushFactory.GenerateMeshFromPolygonsFast(self.GetPolygons(), ref mesh, 0.0f); GameObject gameObject = CreateVolumeMeshCollider(mesh); gameObject.transform.position = transform.position; gameObject.transform.rotation = transform.rotation; // execute custom volume generation code: Volume.OnCreateVolume(gameObject); } }
/// <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) { base.Invalidate(polygonsChanged); if (!gameObject.activeInHierarchy) { return; } // previous versions of sabrecsg used to use mesh colliders for ray collision, but that's no longer the case so we clean them up. MeshCollider[] meshColliders = GetComponents <MeshCollider>(); if (meshColliders.Length > 0) { for (int i = 0; i < meshColliders.Length; i++) { DestroyImmediate(meshColliders[i]); } } // Make sure there is a mesh filter on this object MeshFilter meshFilter = gameObject.AddOrGetComponent <MeshFilter>(); MeshRenderer meshRenderer = gameObject.AddOrGetComponent <MeshRenderer>(); 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 = SabreCSGResources.GetNoCSGMaterial(); } else { if (this.mode == CSGMode.Add) { material = SabreCSGResources.GetAddMaterial(); } else { material = SabreCSGResources.GetSubtractMaterial(); } } if (meshRenderer.sharedMaterial != material) { meshRenderer.sharedMaterial = material; } #endif // isBrushConvex = GeometryHelper.IsBrushConvex(polygons); if (polygonsChanged) { RecalculateBrushCache(); } UpdateVisibility(); objectVersionSerialized++; objectVersionUnserialized = objectVersionSerialized; if (cachedWorldTransform == null) { cachedWorldTransform = new WorldTransformData(transform); } cachedWorldTransform.SetFromTransform(transform); }
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 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; }