diff --git a/Components/Components/Containers/ChiselModelComponent.cs b/Components/Components/Containers/ChiselModelComponent.cs index e323c14..80fdb29 100644 --- a/Components/Components/Containers/ChiselModelComponent.cs +++ b/Components/Components/Containers/ChiselModelComponent.cs @@ -209,6 +209,11 @@ public sealed class ChiselModelComponent : ChiselNodeComponent public const string kCreateColliderComponentsName = nameof(CreateColliderComponents); public const string kAutoRebuildUVsName = nameof(AutoRebuildUVs); public const string kVertexChannelMaskName = nameof(VertexChannelMask); + public const string kSubtractiveEditingName = nameof(SubtractiveEditing); + public const string kSmoothNormalsName = nameof(SmoothNormals); + public const string kSmoothingAngleName = nameof(SmoothingAngle); + public const string kDebugLogBrushesName = nameof(DebugLogBrushes); + public const string kDebugLogOutputName = nameof(DebugLogOutput); public const string kNodeTypeName = "Model"; @@ -235,6 +240,21 @@ public sealed class ChiselModelComponent : ChiselNodeComponent public bool CreateColliderComponents = true; public bool AutoRebuildUVs = true; public VertexChannelFlags VertexChannelMask = VertexChannelFlags.All; + public bool SubtractiveEditing = false; + public bool SmoothNormals = false; + [Range(0, 180)] + public float SmoothingAngle = 45.0f; + [NonSerialized] bool prevSubtractiveEditing; + [NonSerialized] bool prevSmoothNormals; + [NonSerialized] float prevSmoothingAngle; + + #region Debug + // When enabled all brush geometry will be printed out to the console + // at the start of the CSG job update + public bool DebugLogBrushes = false; + // When enabled the generated output mesh data will be printed out after rebuilding + public bool DebugLogOutput = false; + #endregion public ChiselModelComponent() : base() { } @@ -286,7 +306,25 @@ internal override CSGTreeNode RebuildTreeNodes() var instanceID = GetInstanceID(); Node = CSGTree.Create(instanceID: instanceID); return Node; - } + } + + protected override void OnValidateState() + { + base.OnValidateState(); + + if (prevSubtractiveEditing != SubtractiveEditing && generated != null) + { + FlipGeneratedMeshes(); + prevSubtractiveEditing = SubtractiveEditing; + } + + if ((prevSmoothNormals != SmoothNormals || !Mathf.Approximately(prevSmoothingAngle, SmoothingAngle)) && generated != null) + { + SmoothGeneratedMeshes(); + prevSmoothNormals = SmoothNormals; + prevSmoothingAngle = SmoothingAngle; + } + } public override void OnInitialize() { @@ -327,7 +365,66 @@ public override void OnInitialize() name == ChiselModelManager.kGeneratedDefaultModelName) IsDefaultModel = true; - IsInitialized = true; + prevSubtractiveEditing = SubtractiveEditing; + prevSmoothNormals = SmoothNormals; + prevSmoothingAngle = SmoothingAngle; + IsInitialized = true; + } + + void FlipGeneratedMeshes() + { + if (generated == null) + return; + + if (generated.renderables != null) + { + foreach (var renderable in generated.renderables) + if (renderable != null && renderable.sharedMesh) + ChiselMeshUtility.FlipNormals(renderable.sharedMesh); + } + + if (generated.debugVisualizationRenderables != null) + { + foreach (var renderable in generated.debugVisualizationRenderables) + if (renderable != null && renderable.sharedMesh) + ChiselMeshUtility.FlipNormals(renderable.sharedMesh); + } + + if (generated.colliders != null) + { + foreach (var collider in generated.colliders) + if (collider != null && collider.sharedMesh) + ChiselMeshUtility.FlipNormals(collider.sharedMesh); + } + } + + void SmoothGeneratedMeshes() + { + if (generated == null) + return; + + float angle = SmoothNormals ? SmoothingAngle : 0.0f; + + if (generated.renderables != null) + { + foreach (var renderable in generated.renderables) + if (renderable != null && renderable.sharedMesh) + ChiselMeshUtility.SmoothNormals(renderable.sharedMesh, angle); + } + + if (generated.debugVisualizationRenderables != null) + { + foreach (var renderable in generated.debugVisualizationRenderables) + if (renderable != null && renderable.sharedMesh) + ChiselMeshUtility.SmoothNormals(renderable.sharedMesh, angle); + } + + if (generated.colliders != null) + { + foreach (var collider in generated.colliders) + if (collider != null && collider.sharedMesh) + ChiselMeshUtility.SmoothNormals(collider.sharedMesh, angle); + } } #if UNITY_EDITOR diff --git a/Components/Components/Generated/ChiselGeneratedObjects.cs b/Components/Components/Generated/ChiselGeneratedObjects.cs index 71d7886..70fa887 100644 --- a/Components/Components/Generated/ChiselGeneratedObjects.cs +++ b/Components/Components/Generated/ChiselGeneratedObjects.cs @@ -5,6 +5,7 @@ using UnityEngine.Profiling; using Unity.Jobs; using UnityEngine.Pool; +using System.Text; namespace Chisel.Components { @@ -607,6 +608,18 @@ public int FinishMeshUpdates(ChiselModelComponent model, meshUpdates.meshDataArray = default; Profiler.EndSample(); + if (model.SubtractiveEditing) + { + for (int i = 0; i < foundMeshes.Count; i++) + ChiselMeshUtility.FlipNormals(foundMeshes[i]); + } + + if (model.SmoothNormals) + { + for (int i = 0; i < foundMeshes.Count; i++) + ChiselMeshUtility.SmoothNormals(foundMeshes[i], model.SmoothingAngle); + } + // TODO: user meshDataArray data to determine if colliders are visible or not, then we can move this before the Apply Profiler.BeginSample("UpdateColliders"); ChiselColliderObjects.UpdateProperties(model, this.colliders); @@ -660,6 +673,24 @@ public int FinishMeshUpdates(ChiselModelComponent model, colliderDebugVisualization.renderMaterials = new Material[] { ChiselProjectSettings.CollisionSurfacesMaterial }; // }} + if (model.DebugLogOutput) + { + var sb = new System.Text.StringBuilder(); + sb.AppendLine("Output Mesh Debug Info:"); + for (int m = 0; m < foundMeshes.Count; m++) + { + var mesh = foundMeshes[m]; + sb.AppendLine($"Mesh {m} vertices:"); + var verts = mesh.vertices; + for (int v = 0; v < verts.Length; v++) + sb.AppendLine($" v{v}: {verts[v]}"); + var tris = mesh.triangles; + for (int t = 0; t < tris.Length; t += 3) + sb.AppendLine($" t{t / 3}: {tris[t]}, {tris[t + 1]}, {tris[t + 2]}"); + } + UnityEngine.Debug.Log(sb.ToString()); + } + var foundMeshCount = foundMeshes.Count; foundMeshes.Clear(); return foundMeshCount; diff --git a/Components/Utility/ChiselMeshUtility.cs b/Components/Utility/ChiselMeshUtility.cs new file mode 100644 index 0000000..130974f --- /dev/null +++ b/Components/Utility/ChiselMeshUtility.cs @@ -0,0 +1,36 @@ +using System; +using UnityEngine; + +namespace Chisel.Components +{ + internal static class ChiselMeshUtility + { + public static void FlipNormals(Mesh mesh) + { + if (!mesh) + return; + + var normals = mesh.normals; + if (normals != null && normals.Length > 0) + { + for (int i = 0; i < normals.Length; i++) + normals[i] = -normals[i]; + mesh.normals = normals; + } + + for (int s = 0; s < mesh.subMeshCount; s++) + { + var indices = mesh.GetTriangles(s); + Array.Reverse(indices); + mesh.SetTriangles(indices, s); + } + } + + public static void SmoothNormals(Mesh mesh, float angle) + { + if (!mesh) + return; + NormalSolver.RecalculateNormals(mesh, angle); + } + } +} diff --git a/Components/Utility/ChiselMeshUtility.cs.meta b/Components/Utility/ChiselMeshUtility.cs.meta new file mode 100644 index 0000000..c3ddb67 --- /dev/null +++ b/Components/Utility/ChiselMeshUtility.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d34ada735574463d9cdc828678a82cce diff --git a/Components/Utility/NormalSolver.cs b/Components/Utility/NormalSolver.cs new file mode 100644 index 0000000..019939b --- /dev/null +++ b/Components/Utility/NormalSolver.cs @@ -0,0 +1,260 @@ +/* + * The following code was taken from: https://schemingdeveloper.com + * + * Visit our game studio website: http://stopthegnomes.com + * + * License: You may use this code however you see fit, as long as you include this notice + * without any modifications. + * + * You may not publish a paid asset on Unity store if its main function is based on + * the following code, but you may publish a paid asset that uses this code. + * + * If you intend to use this in a Unity store asset or a commercial project, it would + * be appreciated, but not required, if you let me know with a link to the asset. If I + * don't get back to you just go ahead and use it anyway! + */ + +using System.Collections.Generic; +using UnityEngine; + +namespace Chisel.Components +{ + public static class NormalSolver + { + /// + /// Recalculate the normals of a mesh based on an angle threshold. This takes + /// into account distinct vertices that have the same position. + /// + /// + /// + /// The smoothing angle. Note that triangles that already share + /// the same vertex will be smooth regardless of the angle! + /// + public static void RecalculateNormals(this Mesh mesh, float angle) + { + var cosineThreshold = Mathf.Cos(angle * Mathf.Deg2Rad); + + var vertices = mesh.vertices; + var normals = new Vector3[vertices.Length]; + + // Holds the normal of each triangle in each sub mesh. + var triNormals = new Vector3[mesh.subMeshCount][]; + + var dictionary = new Dictionary>(vertices.Length); + + for (var subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; ++subMeshIndex) + { + + var triangles = mesh.GetTriangles(subMeshIndex); + + triNormals[subMeshIndex] = new Vector3[triangles.Length / 3]; + + for (var i = 0; i < triangles.Length; i += 3) + { + int i1 = triangles[i]; + int i2 = triangles[i + 1]; + int i3 = triangles[i + 2]; + + // Calculate the normal of the triangle + Vector3 p1 = vertices[i2] - vertices[i1]; + Vector3 p2 = vertices[i3] - vertices[i1]; + Vector3 normal = Vector3.Cross(p1, p2).normalized; + int triIndex = i / 3; + triNormals[subMeshIndex][triIndex] = normal; + + List entry; + VertexKey key; + + if (!dictionary.TryGetValue(key = new VertexKey(vertices[i1]), out entry)) + { + entry = new List(4); + dictionary.Add(key, entry); + } + entry.Add(new VertexEntry(subMeshIndex, triIndex, i1)); + + if (!dictionary.TryGetValue(key = new VertexKey(vertices[i2]), out entry)) + { + entry = new List(); + dictionary.Add(key, entry); + } + entry.Add(new VertexEntry(subMeshIndex, triIndex, i2)); + + if (!dictionary.TryGetValue(key = new VertexKey(vertices[i3]), out entry)) + { + entry = new List(); + dictionary.Add(key, entry); + } + entry.Add(new VertexEntry(subMeshIndex, triIndex, i3)); + } + } + + // Each entry in the dictionary represents a unique vertex position. + + foreach (var vertList in dictionary.Values) + { + for (var i = 0; i < vertList.Count; ++i) + { + + var sum = new Vector3(); + var lhsEntry = vertList[i]; + + for (var j = 0; j < vertList.Count; ++j) + { + var rhsEntry = vertList[j]; + + if (lhsEntry.VertexIndex == rhsEntry.VertexIndex) + { + sum += triNormals[rhsEntry.MeshIndex][rhsEntry.TriangleIndex]; + } + else + { + // The dot product is the cosine of the angle between the two triangles. + // A larger cosine means a smaller angle. + var dot = Vector3.Dot( + triNormals[lhsEntry.MeshIndex][lhsEntry.TriangleIndex], + triNormals[rhsEntry.MeshIndex][rhsEntry.TriangleIndex]); + if (dot >= cosineThreshold) + { + sum += triNormals[rhsEntry.MeshIndex][rhsEntry.TriangleIndex]; + } + } + } + + normals[lhsEntry.VertexIndex] = sum.normalized; + } + } + + mesh.normals = normals; + } + + private struct VertexKey + { + private readonly long _x; + private readonly long _y; + private readonly long _z; + + // Change this if you require a different precision. + private const int Tolerance = 100000; + + // Magic FNV values. Do not change these. + private const long FNV32Init = 0x811c9dc5; + private const long FNV32Prime = 0x01000193; + + public VertexKey(Vector3 position) + { + _x = (long)(Mathf.Round(position.x * Tolerance)); + _y = (long)(Mathf.Round(position.y * Tolerance)); + _z = (long)(Mathf.Round(position.z * Tolerance)); + } + + public override bool Equals(object obj) + { + var key = (VertexKey)obj; + return _x == key._x && _y == key._y && _z == key._z; + } + + public override int GetHashCode() + { + long rv = FNV32Init; + rv ^= _x; + rv *= FNV32Prime; + rv ^= _y; + rv *= FNV32Prime; + rv ^= _z; + rv *= FNV32Prime; + + return rv.GetHashCode(); + } + } + + private struct VertexEntry + { + public int MeshIndex; + public int TriangleIndex; + public int VertexIndex; + + public VertexEntry(int meshIndex, int triIndex, int vertIndex) + { + MeshIndex = meshIndex; + TriangleIndex = triIndex; + VertexIndex = vertIndex; + } + } + + public static void AutoWeld(this Mesh mesh, float threshold, float bucketStep) + { + Vector3[] oldVertices = mesh.vertices; + Vector3[] newVertices = new Vector3[oldVertices.Length]; + int[] old2new = new int[oldVertices.Length]; + int newSize = 0; + + // Find AABB + Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); + Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue); + for (int i = 0; i < oldVertices.Length; i++) + { + if (oldVertices[i].x < min.x) min.x = oldVertices[i].x; + if (oldVertices[i].y < min.y) min.y = oldVertices[i].y; + if (oldVertices[i].z < min.z) min.z = oldVertices[i].z; + if (oldVertices[i].x > max.x) max.x = oldVertices[i].x; + if (oldVertices[i].y > max.y) max.y = oldVertices[i].y; + if (oldVertices[i].z > max.z) max.z = oldVertices[i].z; + } + + // Make cubic buckets, each with dimensions "bucketStep" + int bucketSizeX = Mathf.FloorToInt((max.x - min.x) / bucketStep) + 1; + int bucketSizeY = Mathf.FloorToInt((max.y - min.y) / bucketStep) + 1; + int bucketSizeZ = Mathf.FloorToInt((max.z - min.z) / bucketStep) + 1; + List[,,] buckets = new List[bucketSizeX, bucketSizeY, bucketSizeZ]; + + // Make new vertices + for (int i = 0; i < oldVertices.Length; i++) + { + // Determine which bucket it belongs to + int x = Mathf.FloorToInt((oldVertices[i].x - min.x) / bucketStep); + int y = Mathf.FloorToInt((oldVertices[i].y - min.y) / bucketStep); + int z = Mathf.FloorToInt((oldVertices[i].z - min.z) / bucketStep); + + // Check to see if it's already been added + if (buckets[x, y, z] == null) + buckets[x, y, z] = new List(); // Make buckets lazily + + for (int j = 0; j < buckets[x, y, z].Count; j++) + { + Vector3 to = newVertices[buckets[x, y, z][j]] - oldVertices[i]; + if (Vector3.SqrMagnitude(to) < threshold) + { + old2new[i] = buckets[x, y, z][j]; + goto skip; // Skip to next old vertex if this one is already there + } + } + + // Add new vertex + newVertices[newSize] = oldVertices[i]; + buckets[x, y, z].Add(newSize); + old2new[i] = newSize; + newSize++; + + skip:; + } + + // Make new triangles + int[] oldTris = mesh.triangles; + int[] newTris = new int[oldTris.Length]; + for (int i = 0; i < oldTris.Length; i++) + { + newTris[i] = old2new[oldTris[i]]; + } + + Vector3[] finalVertices = new Vector3[newSize]; + for (int i = 0; i < newSize; i++) + finalVertices[i] = newVertices[i]; + + mesh.Clear(); + mesh.vertices = finalVertices; + mesh.triangles = newTris; + mesh.RecalculateNormals(); + mesh.Optimize(); + } + } +} diff --git a/Components/Utility/NormalSolver.cs.meta b/Components/Utility/NormalSolver.cs.meta new file mode 100644 index 0000000..b0fed5c --- /dev/null +++ b/Components/Utility/NormalSolver.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 99408dc071d443e38d8f009614c08c24 diff --git a/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs b/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs index 5b35b61..28ccdec 100644 --- a/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs +++ b/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs @@ -5,6 +5,8 @@ using Unity.Profiling; using Unity.Entities; using System.Buffers; +using UnityEngine; +using System.Reflection; namespace Chisel.Core { @@ -634,6 +636,7 @@ public void RunMeshInitJobs(JobHandle dependsOn) var chiselLookupValues = ChiselTreeLookup.Value[this.tree]; ref var brushMeshBlobs = ref ChiselMeshLookup.Value.brushMeshBlobCache; + { #region Build Lookup Tables using (kJobBuildLookupTablesJobProfilerMarker.Auto()) @@ -839,10 +842,61 @@ public void RunMeshInitJobs(JobHandle dependsOn) } public void RunMeshUpdateJobs() - { - var chiselLookupValues = ChiselTreeLookup.Value[this.tree]; + { + var chiselLookupValues = ChiselTreeLookup.Value[this.tree]; ref var brushMeshBlobs = ref ChiselMeshLookup.Value.brushMeshBlobCache; + // Debug logging of all brush geometry when enabled on the model + var modelObj = UnityEngine.Resources.InstanceIDToObject(this.tree.InstanceID); + bool debugLogBrushes = false; + if (modelObj != null) + { + var type = modelObj.GetType(); + var prop = type.GetProperty("DebugLogBrushes", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (prop != null && prop.PropertyType == typeof(bool)) + debugLogBrushes = (bool)prop.GetValue(modelObj); + else + { + var field = type.GetField("DebugLogBrushes", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (field != null && field.FieldType == typeof(bool)) + debugLogBrushes = (bool)field.GetValue(modelObj); + } + } + if (debugLogBrushes) + { + var sb = new System.Text.StringBuilder(); + sb.AppendLine("Brush Debug Info:"); + for (int i = 0; i < Temporaries.brushes.Length; i++) + { + var brushID = Temporaries.brushes[i]; + var brush = CSGTreeBrush.FindNoErrors(brushID); + if (!brush.Valid) + continue; + sb.AppendLine($"Brush {i} Operation: {brush.Operation}"); + var brushMeshBlob = BrushMeshManager.GetBrushMeshBlob(brush.BrushMesh); + if (!brushMeshBlob.IsCreated) + continue; + ref var vertices = ref brushMeshBlob.Value.localVertices; + for (int v = 0; v < vertices.Length; v++) + sb.AppendLine($" v{v}: {vertices[v]}"); + ref var halfEdges = ref brushMeshBlob.Value.halfEdges; + ref var polygons = ref brushMeshBlob.Value.polygons; + for (int p = 0; p < polygons.Length; p++) + { + var polygon = polygons[p]; + sb.Append($" f{p}:"); + for (int e = 0; e < polygon.edgeCount; e++) + { + var edgeIndex = polygon.firstEdge + e; + sb.Append(' '); + sb.Append(halfEdges[edgeIndex].vertexIndex); + } + sb.AppendLine(); + } + } + UnityEngine.Debug.Log(sb.ToString()); + } + #region Perform CSG #region Prepare diff --git a/Editor/ComponentEditors/Containers/ChiselModelEditor.cs b/Editor/ComponentEditors/Containers/ChiselModelEditor.cs index 39217ae..32df53c 100644 --- a/Editor/ComponentEditors/Containers/ChiselModelEditor.cs +++ b/Editor/ComponentEditors/Containers/ChiselModelEditor.cs @@ -49,8 +49,14 @@ internal static bool ValidateActiveModel(MenuCommand menuCommand) readonly static GUIContent kAdditionalSettingsContent = new("Additional Settings"); readonly static GUIContent kGenerationSettingsContent = new("Geometry Output"); readonly static GUIContent kColliderSettingsContent = new("Collider"); + readonly static GUIContent kDebugContent = new("Debug"); readonly static GUIContent kCreateRenderComponentsContents = new("Renderable"); readonly static GUIContent kCreateColliderComponentsContents = new("Collidable"); + readonly static GUIContent kSubtractiveEditingContents = new("Subtractive Editing", "New brushes are created as subtractive when enabled"); + readonly static GUIContent kSmoothNormalsContents = new("Smooth Normals"); + readonly static GUIContent kSmoothingAngleContents = new("Smoothing Angle"); + readonly static GUIContent kDebugLogBrushesContents = new("Debug Log Brushes"); + readonly static GUIContent kDebugLogOutputContents = new("Debug Log Output"); readonly static GUIContent kUnwrapParamsContents = new("UV Generation"); readonly static GUIContent kForceBuildUVsContents = new("Build", "Manually build lightmap UVs for generated meshes. This operation can be slow for more complicated meshes"); @@ -123,11 +129,17 @@ internal static bool ValidateActiveModel(MenuCommand menuCommand) const string kDisplayLightmapKey = "ChiselModelEditor.ShowLightmapSettings"; const string kDisplayChartingKey = "ChiselModelEditor.ShowChartingSettings"; const string kDisplayUnwrapParamsKey = "ChiselModelEditor.ShowUnwrapParams"; + const string kDisplayDebugKey = "ChiselModelEditor.ShowDebugSettings"; SerializedProperty vertexChannelMaskProp; SerializedProperty createRenderComponentsProp; SerializedProperty createColliderComponentsProp; + SerializedProperty subtractiveEditingProp; + SerializedProperty smoothNormalsProp; + SerializedProperty smoothingAngleProp; + SerializedProperty debugLogBrushesProp; + SerializedProperty debugLogOutputProp; SerializedProperty autoRebuildUVsProp; SerializedProperty angleErrorProp; SerializedProperty areaErrorProp; @@ -168,6 +180,7 @@ internal static bool ValidateActiveModel(MenuCommand menuCommand) bool showLightmapSettings; bool showChartingSettings; bool showUnwrapParams; + bool showDebug; UnityEngine.Object[] childNodes; @@ -207,6 +220,7 @@ internal void OnEnable() showLightmapSettings = SessionState.GetBool(kDisplayLightmapKey, true); showChartingSettings = SessionState.GetBool(kDisplayChartingKey, true); showUnwrapParams = SessionState.GetBool(kDisplayUnwrapParamsKey, true); + showDebug = SessionState.GetBool(kDisplayDebugKey, false); if (!target) { @@ -215,9 +229,14 @@ internal void OnEnable() } vertexChannelMaskProp = serializedObject.FindProperty($"{ChiselModelComponent.kVertexChannelMaskName}"); - createRenderComponentsProp = serializedObject.FindProperty($"{ChiselModelComponent.kCreateRenderComponentsName}"); - createColliderComponentsProp = serializedObject.FindProperty($"{ChiselModelComponent.kCreateColliderComponentsName}"); - autoRebuildUVsProp = serializedObject.FindProperty($"{ChiselModelComponent.kAutoRebuildUVsName}"); + createRenderComponentsProp = serializedObject.FindProperty($"{ChiselModelComponent.kCreateRenderComponentsName}"); + createColliderComponentsProp = serializedObject.FindProperty($"{ChiselModelComponent.kCreateColliderComponentsName}"); + subtractiveEditingProp = serializedObject.FindProperty($"{ChiselModelComponent.kSubtractiveEditingName}"); + smoothNormalsProp = serializedObject.FindProperty($"{ChiselModelComponent.kSmoothNormalsName}"); + smoothingAngleProp = serializedObject.FindProperty($"{ChiselModelComponent.kSmoothingAngleName}"); + debugLogBrushesProp = serializedObject.FindProperty($"{ChiselModelComponent.kDebugLogBrushesName}"); + debugLogOutputProp = serializedObject.FindProperty($"{ChiselModelComponent.kDebugLogOutputName}"); + autoRebuildUVsProp = serializedObject.FindProperty($"{ChiselModelComponent.kAutoRebuildUVsName}"); angleErrorProp = serializedObject.FindProperty($"{ChiselModelComponent.kRenderSettingsName}.{ChiselGeneratedRenderSettings.kUVGenerationSettingsName}.{SerializableUnwrapParam.kAngleErrorName}"); areaErrorProp = serializedObject.FindProperty($"{ChiselModelComponent.kRenderSettingsName}.{ChiselGeneratedRenderSettings.kUVGenerationSettingsName}.{SerializableUnwrapParam.kAreaErrorName}"); hardAngleProp = serializedObject.FindProperty($"{ChiselModelComponent.kRenderSettingsName}.{ChiselGeneratedRenderSettings.kUVGenerationSettingsName}.{SerializableUnwrapParam.kHardAngleName}"); @@ -1184,6 +1203,7 @@ public override void OnInspectorGUI() var oldShowGenerationSettings = showGenerationSettings; var oldShowColliderSettings = showColliderSettings; + var oldShowDebug = showDebug; if (gameObjectsSerializedObject != null) gameObjectsSerializedObject.Update(); if (serializedObject != null) serializedObject.Update(); @@ -1206,6 +1226,10 @@ public override void OnInspectorGUI() EditorGUI.indentLevel++; EditorGUILayout.PropertyField(createColliderComponentsProp, kCreateColliderComponentsContents); EditorGUILayout.PropertyField(createRenderComponentsProp, kCreateRenderComponentsContents); + EditorGUILayout.PropertyField(subtractiveEditingProp, kSubtractiveEditingContents); + EditorGUILayout.PropertyField(smoothNormalsProp, kSmoothNormalsContents); + if (smoothNormalsProp.boolValue) + EditorGUILayout.PropertyField(smoothingAngleProp, kSmoothingAngleContents); EditorGUI.BeginDisabledGroup(!createRenderComponentsProp.boolValue); { @@ -1239,6 +1263,16 @@ public override void OnInspectorGUI() } EditorGUILayout.EndFoldoutHeaderGroup(); } + + showDebug = EditorGUILayout.BeginFoldoutHeaderGroup(showDebug, kDebugContent); + if (showDebug) + { + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(debugLogBrushesProp, kDebugLogBrushesContents); + EditorGUILayout.PropertyField(debugLogOutputProp, kDebugLogOutputContents); + EditorGUI.indentLevel--; + } + EditorGUILayout.EndFoldoutHeaderGroup(); } if (EditorGUI.EndChangeCheck()) { @@ -1251,6 +1285,7 @@ public override void OnInspectorGUI() if (showGenerationSettings != oldShowGenerationSettings) SessionState.SetBool(kDisplayGenerationSettingsKey, showGenerationSettings); if (showColliderSettings != oldShowColliderSettings ) SessionState.SetBool(kDisplayColliderSettingsKey, showColliderSettings); + if (showDebug != oldShowDebug ) SessionState.SetBool(kDisplayDebugKey, showDebug); } finally { diff --git a/Editor/ExtensionMethods/ChiselEditorResources.cs b/Editor/ExtensionMethods/ChiselEditorResources.cs index 5e92370..5a21493 100644 --- a/Editor/ExtensionMethods/ChiselEditorResources.cs +++ b/Editor/ExtensionMethods/ChiselEditorResources.cs @@ -242,7 +242,11 @@ static string[] GetResourcePaths() if (System.IO.Directory.Exists(packagePath)) { var localPath = ToLocalPath(packagePath); - if (foundPaths.Add(localPath)) paths.Add(localPath); + if (localPath == null) + continue; + if ((localPath.StartsWith("Assets/") || localPath.StartsWith("Packages/")) && + foundPaths.Add(localPath)) + paths.Add(localPath); } } return paths.ToArray(); @@ -260,21 +264,30 @@ static string ToLocalPath(string path) if (assetsPathIndex != -1) { path = path.Substring(assetsPathIndex + 1); - } else + } + else { var packagePathIndex = path.IndexOf(@"/Packages/"); if (packagePathIndex != -1) { path = path.Substring(packagePathIndex + 1); - } - var packageCacheIndex = path.IndexOf(@"/PackageCache/"); - if (packageCacheIndex != -1) - { - path = "Packages/" + path.Substring(packageCacheIndex + @"/PackageCache/".Length); - } - } + } + else + { + var packageCacheIndex = path.IndexOf(@"/PackageCache/"); + if (packageCacheIndex != -1) + { + path = "Packages/" + + path.Substring(packageCacheIndex + @"/PackageCache/".Length); + } + else + { + return null; // outside project + } + } + } if (!path.EndsWith("/")) - path = path + "/"; + path += "/"; return path; } #endregion diff --git a/Editor/SceneView/ChiselDefaultPlacementTools.cs b/Editor/SceneView/ChiselDefaultPlacementTools.cs index b906394..3ec2a22 100644 --- a/Editor/SceneView/ChiselDefaultPlacementTools.cs +++ b/Editor/SceneView/ChiselDefaultPlacementTools.cs @@ -45,6 +45,8 @@ public override void OnSceneGUI(SceneView sceneView, Rect dragArea) case ShapeExtrusionState.Modified: case ShapeExtrusionState.Create: { + ChiselModelComponent model = generatedComponent ? + generatedComponent.GetComponentInParent() : null; if (!generatedComponent) { if (height != 0) @@ -52,7 +54,7 @@ public override void OnSceneGUI(SceneView sceneView, Rect dragArea) var center2D = shape.Center; var center3D = new Vector3(center2D.x, 0, center2D.y); Transform parentTransform = null; - var model = ChiselModelManager.Instance.GetActiveModelOrCreate(modelBeneathCursor); + model = ChiselModelManager.Instance.GetActiveModelOrCreate(modelBeneathCursor); if (model != null) parentTransform = model.transform; generatedComponent = ChiselComponentFactory.Create(generatorType, ToolName, parentTransform, transformation * Matrix4x4.TRS(center3D, Quaternion.identity, Vector3.one)) @@ -67,6 +69,8 @@ public override void OnSceneGUI(SceneView sceneView, Rect dragArea) } } else { + if (!model) + model = generatedComponent.GetComponentInParent(); generatedComponent.Operation = forceOperation ?? ((height < 0 && modelBeneathCursor) ? CSGOperationType.Subtractive : @@ -135,6 +139,8 @@ public override void OnSceneGUI(SceneView sceneView, Rect dragArea) { case GeneratorModeState.Update: { + ChiselModelComponent model = generatedComponent ? + generatedComponent.GetComponentInParent() : null; if (!generatedComponent) { var size = bounds.size; @@ -144,7 +150,7 @@ public override void OnSceneGUI(SceneView sceneView, Rect dragArea) { // Create the generator GameObject Transform parentTransform = null; - var model = ChiselModelManager.Instance.GetActiveModelOrCreate(modelBeneathCursor); + model = ChiselModelManager.Instance.GetActiveModelOrCreate(modelBeneathCursor); if (model != null) parentTransform = model.transform; generatedComponent = ChiselComponentFactory.Create(generatorType, ToolName, parentTransform, transformation) as ChiselNodeGeneratorComponent; @@ -176,12 +182,17 @@ public override void OnSceneGUI(SceneView sceneView, Rect dragArea) // Update the generator GameObject ChiselComponentFactory.SetTransform(generatedComponent, transformation); if ((generatoreModeFlags & PlacementFlags.AlwaysFaceUp) == PlacementFlags.AlwaysFaceCameraXZ) + { generatedComponent.Operation = forceOperation ?? CSGOperationType.Additive; - else + } else + { + if (!model) + model = generatedComponent.GetComponentInParent(); generatedComponent.Operation = forceOperation ?? ((height < 0 && modelBeneathCursor) ? CSGOperationType.Subtractive : CSGOperationType.Additive); + } PlacementToolDefinition.OnUpdate(ref generatedComponent.definition, bounds); generatedComponent.OnValidate(); diff --git a/Package Resources/Materials/URP/DebugVisualization/Collision.mat b/Package Resources/Materials/URP/DebugVisualization/Collision.mat index 504eb34..e146b72 100644 --- a/Package Resources/Materials/URP/DebugVisualization/Collision.mat +++ b/Package Resources/Materials/URP/DebugVisualization/Collision.mat @@ -12,7 +12,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} m_Name: m_EditorClassIdentifier: - version: 9 + version: 10 --- !u!21 &2100000 Material: serializedVersion: 8 diff --git a/Package Resources/Materials/URP/DebugVisualization/Discarded.mat b/Package Resources/Materials/URP/DebugVisualization/Discarded.mat index 86cf889..0fa8224 100644 --- a/Package Resources/Materials/URP/DebugVisualization/Discarded.mat +++ b/Package Resources/Materials/URP/DebugVisualization/Discarded.mat @@ -157,4 +157,4 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} m_Name: m_EditorClassIdentifier: - version: 9 + version: 10 diff --git a/Package Resources/Materials/URP/DebugVisualization/ShadowCasting.mat b/Package Resources/Materials/URP/DebugVisualization/ShadowCasting.mat index 329315d..b053c96 100644 --- a/Package Resources/Materials/URP/DebugVisualization/ShadowCasting.mat +++ b/Package Resources/Materials/URP/DebugVisualization/ShadowCasting.mat @@ -12,7 +12,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} m_Name: m_EditorClassIdentifier: - version: 9 + version: 10 --- !u!114 &-1506755939743460727 MonoBehaviour: m_ObjectHideFlags: 1 diff --git a/Package Resources/Materials/URP/DebugVisualization/ShadowOnly.mat b/Package Resources/Materials/URP/DebugVisualization/ShadowOnly.mat index 4903e4b..754e9c5 100644 --- a/Package Resources/Materials/URP/DebugVisualization/ShadowOnly.mat +++ b/Package Resources/Materials/URP/DebugVisualization/ShadowOnly.mat @@ -157,4 +157,4 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} m_Name: m_EditorClassIdentifier: - version: 9 + version: 10 diff --git a/Package Resources/Materials/URP/DebugVisualization/ShadowReceiving.mat b/Package Resources/Materials/URP/DebugVisualization/ShadowReceiving.mat index 149e24e..37dc475 100644 --- a/Package Resources/Materials/URP/DebugVisualization/ShadowReceiving.mat +++ b/Package Resources/Materials/URP/DebugVisualization/ShadowReceiving.mat @@ -12,7 +12,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} m_Name: m_EditorClassIdentifier: - version: 9 + version: 10 --- !u!114 &-2819722843381972991 MonoBehaviour: m_ObjectHideFlags: 1 diff --git a/Package Resources/Materials/URP/DebugVisualization/Trigger.mat b/Package Resources/Materials/URP/DebugVisualization/Trigger.mat index 41a5731..e4be9a8 100644 --- a/Package Resources/Materials/URP/DebugVisualization/Trigger.mat +++ b/Package Resources/Materials/URP/DebugVisualization/Trigger.mat @@ -28,7 +28,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} m_Name: m_EditorClassIdentifier: - version: 9 + version: 10 --- !u!21 &2100000 Material: serializedVersion: 8 diff --git a/Package Resources/Materials/URP/DebugVisualization/UserHidden.mat b/Package Resources/Materials/URP/DebugVisualization/UserHidden.mat index 47b0e35..83dd607 100644 --- a/Package Resources/Materials/URP/DebugVisualization/UserHidden.mat +++ b/Package Resources/Materials/URP/DebugVisualization/UserHidden.mat @@ -28,7 +28,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} m_Name: m_EditorClassIdentifier: - version: 9 + version: 10 --- !u!21 &2100000 Material: serializedVersion: 8 diff --git a/Package Resources/Materials/URP/Defaults/Floor.mat b/Package Resources/Materials/URP/Defaults/Floor.mat index 3a23368..15dcce1 100644 --- a/Package Resources/Materials/URP/Defaults/Floor.mat +++ b/Package Resources/Materials/URP/Defaults/Floor.mat @@ -157,4 +157,4 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} m_Name: m_EditorClassIdentifier: - version: 9 + version: 10 diff --git a/Package Resources/Materials/URP/Defaults/Step.mat b/Package Resources/Materials/URP/Defaults/Step.mat index e29993e..03348b3 100644 --- a/Package Resources/Materials/URP/Defaults/Step.mat +++ b/Package Resources/Materials/URP/Defaults/Step.mat @@ -157,4 +157,4 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} m_Name: m_EditorClassIdentifier: - version: 9 + version: 10 diff --git a/Package Resources/Materials/URP/Defaults/Tread.mat b/Package Resources/Materials/URP/Defaults/Tread.mat index 21bc68f..6f92701 100644 --- a/Package Resources/Materials/URP/Defaults/Tread.mat +++ b/Package Resources/Materials/URP/Defaults/Tread.mat @@ -157,4 +157,4 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} m_Name: m_EditorClassIdentifier: - version: 9 + version: 10 diff --git a/Package Resources/Materials/URP/Defaults/Wall.mat b/Package Resources/Materials/URP/Defaults/Wall.mat index 4e30756..86326ec 100644 --- a/Package Resources/Materials/URP/Defaults/Wall.mat +++ b/Package Resources/Materials/URP/Defaults/Wall.mat @@ -12,7 +12,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} m_Name: m_EditorClassIdentifier: - version: 9 + version: 10 --- !u!114 &-1617261777445278378 MonoBehaviour: m_ObjectHideFlags: 1 diff --git a/Package Resources/Materials/builtin/Defaults/Wall.mat b/Package Resources/Materials/builtin/Defaults/Wall.mat index 228422d..9fe2409 100644 --- a/Package Resources/Materials/builtin/Defaults/Wall.mat +++ b/Package Resources/Materials/builtin/Defaults/Wall.mat @@ -24,7 +24,7 @@ Material: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_Name: Wall - m_Shader: {fileID: 45, guid: 0000000000000000f000000000000000, type: 0} + m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} m_Parent: {fileID: 0} m_ModifiedSerializedProperties: 0 m_ValidKeywords: @@ -34,8 +34,10 @@ Material: m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 m_CustomRenderQueue: -1 - stringTagMap: {} - disabledShaderPasses: [] + stringTagMap: + RenderType: Opaque + disabledShaderPasses: + - MOTIONVECTORS m_LockedProperties: m_SavedProperties: serializedVersion: 3 @@ -44,6 +46,10 @@ Material: m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} - _BumpMap: m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} @@ -65,7 +71,7 @@ Material: m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} - _MainTex: - m_Texture: {fileID: 2800000, guid: 449ae3f15d4b14242bd974e568363f23, type: 3} + m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} - _MetallicGlossMap: @@ -84,13 +90,36 @@ Material: m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} m_Ints: [] m_Floats: - : 1 + - _AddPrecomputedVelocity: 0 + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 - _DetailNormalMapScale: 1 - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 - _GlossMapScale: 1 - _Glossiness: 0.183 - _GlossyReflections: 1 @@ -98,13 +127,21 @@ Material: - _Mode: 0 - _OcclusionStrength: 1 - _Parallax: 0.02 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 - _SmoothnessTextureChannel: 0 - _SpecularHighlights: 0 - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 - _UVSec: 0 + - _WorkflowMode: 1 + - _XRMotionVectorsPass: 1 - _ZWrite: 1 m_Colors: - : {r: 1, g: 1, b: 1, a: 1} + - _BaseColor: {r: 1, g: 1, b: 1, a: 1} - _Color: {r: 1, g: 1, b: 1, a: 1} - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} - _SpecColor: {r: 0.09433961, g: 0.09433961, b: 0.09433961, a: 1} diff --git a/README.md b/README.md index 713a49f..48e44ca 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,14 @@ Features (incomplete) * Draw 2D shapes (possible to turn straight lines into curves) on existing CSG surfaces and extrude them * Precise snapping to surfaces, edges, vertices and grid lines * Rotatable & movable grid +* Subtractive Workflow **(NEW)** +* Normal smoothing **(NEW)** Planned Features (incomplete, and in random order): * [Debug Visualizations](https://github.com/RadicalCSG/Chisel.Prototype/issues/118) to see shadow only surfaces, collider surfaces etc. (partially implemented) * [Double sided surfaces](https://github.com/RadicalCSG/Chisel.Prototype/issues/226) * [Extrusion from existing surface](https://github.com/RadicalCSG/Chisel.Prototype/issues/19) -* [Subtractive Workflow](https://github.com/RadicalCSG/Chisel.Prototype/issues/14) * [Clip Tool](https://github.com/RadicalCSG/Chisel.Prototype/issues/15) -* [Normal smoothing](https://github.com/RadicalCSG/Chisel.Prototype/issues/184) * [Node Based Generators](https://github.com/RadicalCSG/Chisel.Prototype/issues/94) for easy procedural content generation * [2D shape editor](https://github.com/RadicalCSG/Chisel.Prototype/issues/260) * [Hotspot mapping](https://github.com/RadicalCSG/Chisel.Prototype/issues/173)