Skip to content

Crack stitching #372

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Plugins/API/Components/Runtime/CSGModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum ModelSettingsFlags
StitchLightmapSeams = 4096,
IgnoreNormals = 8192,
TwoSidedShadows = 16384,
AutoStitchCracks = 32768
}

[Serializable]
Expand Down Expand Up @@ -61,6 +62,7 @@ public sealed class CSGModel : CSGNode
public bool NeedAutoUpdateRigidBody { get { return (Settings & ModelSettingsFlags.AutoUpdateRigidBody) == (ModelSettingsFlags)0; } }
public bool PreserveUVs { get { return (Settings & ModelSettingsFlags.PreserveUVs) != (ModelSettingsFlags)0; } }
public bool StitchLightmapSeams { get { return (Settings & ModelSettingsFlags.StitchLightmapSeams) != (ModelSettingsFlags)0; } }
public bool AutoStitchCracks { get { return (Settings & ModelSettingsFlags.AutoStitchCracks) != (ModelSettingsFlags)0; } }
public bool AutoRebuildUVs { get { return (Settings & ModelSettingsFlags.AutoRebuildUVs) != (ModelSettingsFlags)0; } }
public bool IgnoreNormals { get { return (Settings & ModelSettingsFlags.IgnoreNormals) != (ModelSettingsFlags)0; } }

Expand Down
541 changes: 541 additions & 0 deletions Plugins/Editor/Scripts/Control/Helpers/CracksStitching.cs

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions Plugins/Editor/Scripts/Control/Helpers/CracksStitching.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

182 changes: 180 additions & 2 deletions Plugins/Editor/Scripts/Control/Managers/MeshInstanceManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//#define SHOW_GENERATED_MESHES
using System.Linq;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEditor;
Expand Down Expand Up @@ -894,8 +895,7 @@ private static void GenerateLightmapUVsForInstance(GeneratedMeshInstance instanc
meshRendererComponent.realtimeLightmapIndex = -1;
meshRendererComponent.lightmapIndex = -1;

var oldVertices = instance.SharedMesh.vertices;
if (oldVertices.Length == 0)
if (instance.SharedMesh.vertexCount == 0)
return;

var tempMesh = instance.SharedMesh.Clone();
Expand Down Expand Up @@ -926,6 +926,167 @@ private static bool NeedToGenerateLightmapUVsForInstance(GeneratedMeshInstance i
return !instance.HasUV2 && instance.RenderSurfaceType == RenderSurfaceType.Normal;
}

public static bool NeedToStitchCracksForModel(CSGModel model)
{
if (!ModelTraits.IsModelEditable(model))
return false;

if (!model.generatedMeshes)
return false;

var container = model.generatedMeshes;
if (!container || container.owner != model)
return false;

foreach (var instance in container.MeshInstances)
{
if (!instance)
continue;

if (NeedToStitchCracksForInstance(instance))
return true;
}
return false;
}

public static void StitchCracksForModel(CSGModel model)
{
if (!ModelTraits.IsModelEditable(model))
return;

if (!model.generatedMeshes)
return;

var container = model.generatedMeshes;
if (!container || !container.owner)
return;

if (!container.HasMeshInstances)
return;

foreach (var instance in container.MeshInstances)
{
if (!instance)
continue;
if (!instance.SharedMesh)
instance.FindMissingSharedMesh();
}

foreach (var grouping in from x in container.MeshInstances
where x.SharedMesh != null && x.SharedMesh.vertexCount > 0
group x by x.RenderSurfaceType == RenderSurfaceType.Collider)
{
var meshes = new List<Mesh>();
foreach (var instance in grouping)
{
instance.SharedMesh = instance.SharedMesh.Clone();
meshes.Add(instance.SharedMesh);
instance.CracksSolverCancellation?.Invoke();
}

if (meshes.Count == 0)
continue;

var tokenSource = new CancellationTokenSource();
string key = $"Stitching {container.name}'s {(grouping.Key ? "Colliders" : "Meshes")}";

#if UNITY_2020_OR_NEWER
int progressId = Progress.Start(key);
var progressLogger = new CracksProgressLogger(progressId);
EditorApplication.update += progressLogger.Update;
foreach (var instance in grouping)
{
instance.CracksSolverCancellation += () =>
{
tokenSource.Cancel();
Progress.Finish(progressId);
instance.CracksSolverCancellation = null;
EditorApplication.update -= progressLogger.Update;
};
}
#else
foreach (var instance in grouping)
{
instance.CracksSolverCancellation += () =>
{
tokenSource.Cancel();
instance.CracksSolverCancellation = null;
};
}
CracksStitching.ISolverDebugProvider progressLogger = null;
#endif

CracksStitching.SolveAsync(meshes.ToArray(), progressLogger, tokenSource.Token,
() =>
{
foreach (var instance in grouping)
{
EditorSceneManager.MarkSceneDirty(instance.gameObject.scene);
instance.CracksSolverCancellation?.Invoke();
}
#if !UNITY_2020_OR_NEWER
Debug.Log($"Finished {key}");
#endif
});

foreach (var instance in grouping)
{
instance.CracksHashValue = instance.MeshDescription.geometryHashValue;
instance.HasNoCracks = true;
}
}
}

#if UNITY_2020_OR_NEWER
private class CracksProgressLogger : CracksStitching.ISolverDebugProvider
{
CracksStitching.WorkingData data;
int progressId;

public CracksProgressLogger(int pProgressId)
{
progressId = pProgressId;
}

public void Update()
{
if (data.edgesLeft == null)
{
Progress.Report(progressId, 0f );
}
else
{
// DATA IS NOT THREAD SAFE, BE VERY CAREFUL WITH HOW YOU READ STUFF FROM IT
Progress.Report(progressId, 1.0f - ((float)data.edgesLeft.Count / data.allEdges.Count) );
}
}

public void HookIntoWorkingData(CracksStitching.WorkingData pData) => data = pData;
public void LogWarning(string str){ }
public void Log(string str){ }
}
#endif

/// <summary> Thin helper to debug issues related to crack stitching </summary>
private class CracksDebugger : CracksStitching.ISolverDebugProvider
{
public void HookIntoWorkingData(CracksStitching.WorkingData data){ }
public void LogWarning(string str) => Debug.LogWarning(str);
public void Log(string str){ }
}

private static bool NeedToStitchCracksForInstance(GeneratedMeshInstance instance)
{
return !instance.HasNoCracks;
}

public static void ClearCrackStitching(GeneratedMeshInstance instance)
{
instance.CracksHashValue = 0;
instance.HasNoCracks = false;
instance.CracksSolverCancellation?.Invoke();
}

private static bool AreBoundsEmpty(GeneratedMeshInstance instance)
{
return
Expand Down Expand Up @@ -1203,6 +1364,22 @@ public static void Refresh(GeneratedMeshInstance instance, CSGModel owner, bool
}
}
}

if (instance.HasNoCracks && instance.CracksHashValue != instance.MeshDescription.geometryHashValue && meshRendererComponent)
{
instance.ResetStitchCracksTime = Time.realtimeSinceStartup;
if(instance.HasNoCracks)
ClearCrackStitching(instance);
}

if (owner.AutoStitchCracks || postProcessScene)
{
if ((float.IsPositiveInfinity(instance.ResetStitchCracksTime) || Time.realtimeSinceStartup - instance.ResetStitchCracksTime > 2.0f) &&
NeedToStitchCracksForModel(owner))
{
StitchCracksForModel(owner);
}
}

if (!postProcessScene &&
meshFilterComponent.sharedMesh != instance.SharedMesh)
Expand Down Expand Up @@ -1345,6 +1522,7 @@ public static void Refresh(GeneratedMeshInstance instance, CSGModel owner, bool
meshRendererComponent.hideFlags = HideFlags.None; UnityEngine.Object.DestroyImmediate(meshRendererComponent); instance.Dirty = true;
}
instance.LightingHashValue = instance.MeshDescription.geometryHashValue;
instance.CracksHashValue = instance.MeshDescription.geometryHashValue;
meshFilterComponent = null;
meshRendererComponent = null;
instance.CachedMeshRendererSO = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ public static void OnInspectorGUI(UnityEngine.Object[] targets)
bool? DoNotRender = (Settings & ModelSettingsFlags.DoNotRender) == ModelSettingsFlags.DoNotRender;
bool? TwoSidedShadows = (Settings & ModelSettingsFlags.TwoSidedShadows) == ModelSettingsFlags.TwoSidedShadows;
// bool? ReceiveShadows = !((settings & ModelSettingsFlags.DoNotReceiveShadows) == ModelSettingsFlags.DoNotReceiveShadows);
bool? AutoStitchCracks = (Settings & ModelSettingsFlags.AutoStitchCracks) == ModelSettingsFlags.AutoStitchCracks;
bool? AutoRebuildUVs = (Settings & ModelSettingsFlags.AutoRebuildUVs) == ModelSettingsFlags.AutoRebuildUVs;
bool? PreserveUVs = (Settings & ModelSettingsFlags.PreserveUVs) == ModelSettingsFlags.PreserveUVs;
bool? StitchLightmapSeams = (Settings & ModelSettingsFlags.StitchLightmapSeams) == ModelSettingsFlags.StitchLightmapSeams;
Expand Down Expand Up @@ -266,6 +267,7 @@ public static void OnInspectorGUI(UnityEngine.Object[] targets)
bool currDoNotRender = (Settings & ModelSettingsFlags.DoNotRender) == ModelSettingsFlags.DoNotRender;
bool currTwoSidedShadows = (Settings & ModelSettingsFlags.TwoSidedShadows) == ModelSettingsFlags.TwoSidedShadows;
// bool currReceiveShadows = !((settings & ModelSettingsFlags.DoNotReceiveShadows) == ModelSettingsFlags.DoNotReceiveShadows);
bool currAutoStitchCracks = (Settings & ModelSettingsFlags.AutoStitchCracks) == ModelSettingsFlags.AutoStitchCracks;
bool currAutoRebuildUVs = (Settings & ModelSettingsFlags.AutoRebuildUVs) == ModelSettingsFlags.AutoRebuildUVs;
bool currPreserveUVs = (Settings & ModelSettingsFlags.PreserveUVs) == ModelSettingsFlags.PreserveUVs;
bool currStitchLightmapSeams = (Settings & ModelSettingsFlags.StitchLightmapSeams) == ModelSettingsFlags.StitchLightmapSeams;
Expand Down Expand Up @@ -303,6 +305,7 @@ public static void OnInspectorGUI(UnityEngine.Object[] targets)
if (TwoSidedShadows .HasValue && TwoSidedShadows .Value != currTwoSidedShadows ) TwoSidedShadows = null;
// if (ReceiveShadows .HasValue && ReceiveShadows .Value != currReceiveShadows ) ReceiveShadows = null;
// if (ShadowCastingMode .HasValue && ShadowCastingMode .Value != currShadowCastingMode ) ShadowCastingMode = null;
if (AutoStitchCracks .HasValue && AutoStitchCracks .Value != currAutoStitchCracks ) AutoStitchCracks = null;
if (AutoRebuildUVs .HasValue && AutoRebuildUVs .Value != currAutoRebuildUVs ) AutoRebuildUVs = null;
if (PreserveUVs .HasValue && PreserveUVs .Value != currPreserveUVs ) PreserveUVs = null;
if (StitchLightmapSeams .HasValue && StitchLightmapSeams .Value != currStitchLightmapSeams ) StitchLightmapSeams = null;
Expand Down Expand Up @@ -1130,6 +1133,27 @@ public static void OnInspectorGUI(UnityEngine.Object[] targets)
EditorApplication.RepaintHierarchyWindow();
EditorApplication.DirtyHierarchyWindowSorting();
}
{
var autoStitchCracks = AutoStitchCracks ?? false;
EditorGUI.BeginChangeCheck();
{
EditorGUI.showMixedValue = !AutoStitchCracks.HasValue;
autoStitchCracks = EditorGUILayout.Toggle(StitchCracksContent, autoStitchCracks);
}
if (EditorGUI.EndChangeCheck())
{
for (int i = 0; i < models.Length; i++)
{
if (autoStitchCracks)
models[i].Settings |= ModelSettingsFlags.AutoStitchCracks;
else
models[i].Settings &= ~ModelSettingsFlags.AutoStitchCracks;
MeshInstanceManager.Refresh(models[i], onlyFastRefreshes: false);
}
GUI.changed = true;
AutoStitchCracks = autoStitchCracks;
}
}

#if UNITY_2017_3_OR_NEWER
GUILayout.Space(10);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ internal sealed partial class CSGModelComponentInspectorGUI

private static readonly GUIContent MinimumChartSizeContent = new GUIContent("Min Chart Size", "Specifies the minimum texel size used for a UV chart. If stitching is required, a value of 4 will create a chart of 4x4 texels to store lighting and directionality. If stitching is not required, a value of 2 will reduce the texel density and provide better lighting build times and run time performance.");

private static readonly GUIContent StitchCracksContent = new GUIContent("Stitch Cracks", "Fill in cracks that came out through the mesh generation process, increases the amount of triangles.");

public static int[] MinimumChartSizeValues = { 2, 4 };
public static GUIContent[] MinimumChartSizeStrings =
{
Expand Down
16 changes: 12 additions & 4 deletions Plugins/Runtime/Scripts/Components/GeneratedMeshInstance.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using UnityEngine;
using UnityEngine.Serialization;
using MeshQuery = RealtimeCSG.Foundation.MeshQuery;
Expand Down Expand Up @@ -130,10 +131,14 @@ public sealed class GeneratedMeshInstance : MonoBehaviour

[HideInInspector] public bool HasGeneratedNormals = false;
[HideInInspector] public bool HasUV2 = false;
[NonSerialized]
[HideInInspector] public bool HasNoCracks = false;
[NonSerialized]
[HideInInspector] public float ResetUVTime = float.PositiveInfinity;
[HideInInspector] public float ResetStitchCracksTime = float.PositiveInfinity;
[HideInInspector] public Int64 LightingHashValue;

[HideInInspector] public Int64 CracksHashValue;

[NonSerialized] public Action CracksSolverCancellation;
[NonSerialized] [HideInInspector] public bool Dirty = true;
[NonSerialized] [HideInInspector] public MeshCollider CachedMeshCollider;
[NonSerialized] [HideInInspector] public MeshFilter CachedMeshFilter;
Expand All @@ -145,11 +150,14 @@ public void Reset()
RenderMaterial = null;
PhysicsMaterial = null;
RenderSurfaceType = (RenderSurfaceType)999;
HasGeneratedNormals = false;
HasUV2 = false;
ResetUVTime = float.PositiveInfinity;
ResetUVTime = float.PositiveInfinity;
ResetStitchCracksTime = float.PositiveInfinity;
LightingHashValue = 0;
CracksHashValue = 0;
CracksSolverCancellation?.Invoke();

Dirty = true;

Expand Down