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)