Skip to content
Open
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
75 changes: 73 additions & 2 deletions Components/Components/Containers/ChiselModelComponent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

using Chisel.Core;
Expand Down Expand Up @@ -107,6 +109,9 @@ public sealed class ChiselGeneratedRenderSettings
public const string kLightProbeVolumeOverrideName = nameof(lightProbeProxyVolumeOverride);
public const string kProbeAnchorName = nameof(probeAnchor);
public const string kReceiveGIName = nameof(receiveGI);
public const string kSubtractiveWorkflowName = nameof(subtractiveWorkflow);
public const string kNormalSmoothingName = nameof(normalSmoothing);
public const string kNormalSmoothingAngleName = nameof(normalSmoothingAngle);

#if UNITY_EDITOR
public const string kLightmapParametersName = nameof(lightmapParameters);
Expand All @@ -128,6 +133,9 @@ public sealed class ChiselGeneratedRenderSettings
public bool allowOcclusionWhenDynamic = true;
public uint renderingLayerMask = ~(uint)0;
public ReceiveGI receiveGI = ReceiveGI.LightProbes;
public bool subtractiveWorkflow = false;
public bool normalSmoothing = false;
[Range(0, 180)] public float normalSmoothingAngle = 45.0f;

#if UNITY_EDITOR
// SerializedObject access Only
Expand Down Expand Up @@ -174,6 +182,9 @@ public void Reset()
allowOcclusionWhenDynamic = true;
renderingLayerMask = ~(uint)0;
receiveGI = ReceiveGI.LightProbes;
subtractiveWorkflow = false;
normalSmoothing = false;
normalSmoothingAngle = 45.0f;
#if UNITY_EDITOR
lightmapParameters = new UnityEditor.LightmapParameters();
importantGI = false;
Expand Down Expand Up @@ -231,7 +242,7 @@ public sealed class ChiselModelComponent : ChiselNodeComponent


// TODO: put all bools in flags (makes it harder to work with in the ModelEditor though)
public bool CreateRenderComponents = true;
public bool CreateRenderComponents = true;
public bool CreateColliderComponents = true;
public bool AutoRebuildUVs = true;
public VertexChannelFlags VertexChannelMask = VertexChannelFlags.All;
Expand All @@ -241,7 +252,7 @@ public ChiselModelComponent() : base() { }


// Will show a warning icon in hierarchy when generator has a problem (do not make this method slow, it is called a lot!)
public override void GetMessages(IChiselMessageHandler messages)
public override void GetMessages(IChiselMessageHandler messages)
{
// TODO: improve warning messages
const string kModelHasNoChildrenMessage = kNodeTypeName + " has no children and will not have an effect";
Expand Down Expand Up @@ -271,6 +282,7 @@ public override void GetMessages(IChiselMessageHandler messages)

protected override void OnCleanup()
{
ModelSettingsStore.Remove(GetInstanceID());
if (generated != null)
{
if (!this && generated.generatedDataContainer)
Expand Down Expand Up @@ -303,6 +315,7 @@ public override void OnInitialize()
renderSettings = new ChiselGeneratedRenderSettings();
renderSettings.Reset();
}
UpdateModelSettingsLookup();

if (generated != null &&
!generated.generatedDataContainer)
Expand Down Expand Up @@ -330,6 +343,64 @@ public override void OnInitialize()
IsInitialized = true;
}

void MarkAllBrushesDirty()
{
if (!Node.Valid)
return;

var stack = new Stack<CSGTreeNode>();
stack.Push(Node);
while (stack.Count > 0)
{
var current = stack.Pop();
if (!current.Valid)
continue;

switch (current.Type)
{
case CSGNodeType.Brush:
((CSGTreeBrush)current).SetDirty();
break;
case CSGNodeType.Branch:
case CSGNodeType.Tree:
for (int i = 0; i < current.Count; i++)
stack.Push(current[i]);
break;
}
}
}

internal void UpdateModelSettingsLookup()
{
if (renderSettings == null)
{
renderSettings = new ChiselGeneratedRenderSettings();
renderSettings.Reset();
}

ModelSettingsStore.Set(GetInstanceID(), new ModelSettings
{
SubtractiveWorkflow = renderSettings.subtractiveWorkflow,
NormalSmoothing = renderSettings.normalSmoothing,
NormalSmoothingAngle = renderSettings.normalSmoothingAngle
});
SetDirty();
MarkAllBrushesDirty();
if (ChiselModelManager.Instance != null)
ChiselModelManager.Instance.UpdateModels();
}

protected override void OnValidateState()
{
base.OnValidateState();
UpdateModelSettingsLookup();
}

public void SyncModelSettingsStore()
{
UpdateModelSettingsLookup();
}

#if UNITY_EDITOR
// TODO: remove from here, shouldn't be public
public MaterialPropertyBlock materialPropertyBlock;
Expand Down
102 changes: 93 additions & 9 deletions Core/2.Processing/Jobs/GenerateSurfaceTrianglesJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Unity.Mathematics;
using Debug = UnityEngine.Debug;
using ReadOnlyAttribute = Unity.Collections.ReadOnlyAttribute;
using WriteOnlyAttribute = Unity.Collections.WriteOnlyAttribute;
using Unity.Entities;
using andywiecko.BurstTriangulator.LowLevel.Unsafe;
using andywiecko.BurstTriangulator;
Expand All @@ -24,6 +23,8 @@ struct GenerateSurfaceTrianglesJob : IJobParallelForDefer
[NoAlias, ReadOnly] public NativeStream.Reader input;
[NoAlias, ReadOnly] public NativeArray<MeshQuery> meshQueries;
[NoAlias, ReadOnly] public CompactHierarchyManagerInstance.ReadOnlyInstanceIDLookup instanceIDLookup;
[NoAlias, ReadOnly] public bool subtractiveWorkflow;
[NoAlias, ReadOnly] public float normalSmoothingAngle;

// Write
[NativeDisableParallelForRestriction]
Expand Down Expand Up @@ -123,8 +124,6 @@ public unsafe void Execute(int index)
}
input.EndForEachIndex();



if (!basePolygonCache[brushNodeOrder].IsCreated)
return;

Expand All @@ -141,7 +140,6 @@ public unsafe void Execute(int index)
maxLoops = math.max(maxLoops, length);
}


ref var baseSurfaces = ref basePolygonCache[brushNodeOrder].Value.surfaces;
var transform = transformationCache[brushNodeOrder];
var treeToNode = transform.treeToNode;
Expand Down Expand Up @@ -226,6 +224,14 @@ public unsafe void Execute(int index)
var planeNormalMap = math.mul(nodeToTreeInvTrans, plane);
var map3DTo2D = new Map3DTo2D(planeNormalMap.xyz);

// Normal flip logic preparation
float3 finalFaceNormal = map3DTo2D.normal;
if (subtractiveWorkflow)
{
// Flip the normal direction so lighting is correct for the "inside"
finalFaceNormal = -finalFaceNormal;
}

surfaceIndexList.Clear();
for (int li = 0; li < loops.Length; li++)
{
Expand Down Expand Up @@ -268,6 +274,7 @@ public unsafe void Execute(int index)
output,
settings,
Allocator.Temp);

#if UNITY_EDITOR && DEBUG
// Inside the loop after calling Triangulate:
if (output.Status.Value != Status.OK)
Expand All @@ -283,17 +290,92 @@ public unsafe void Execute(int index)
if (output.Status.Value != Status.OK || output.Triangles.Length == 0)
continue;

// Winding order flip for subtractive
if (subtractiveWorkflow)
{
// Flip winding order (0,1,2 -> 0,2,1) to face inwards
for (int ti = 0; ti < output.Triangles.Length; ti += 3)
{
(output.Triangles[ti + 1], output.Triangles[ti + 2]) =
(output.Triangles[ti + 2], output.Triangles[ti + 1]);
}
}

// Map triangles back
var prevCount = surfaceIndexList.Length;
var interiorCat = (CategoryIndex)info.interiorCategory;
roVerts.RemapTriangles(interiorCat, output.Triangles, surfaceIndexList);

// Register vertices (Pass calculated/flipped normal)
uniqueVertexMapper.RegisterVertices(
surfaceIndexList,
prevCount,
*brushVertices.m_Vertices,
map3DTo2D.normal,
finalFaceNormal,
instanceID,
interiorCat);

// Normal smoothing logic (across the entire object)
if (normalSmoothingAngle > 0.0001f)
{
var renderVertices = uniqueVertexMapper.surfaceRenderVertices;
var positions = uniqueVertexMapper.surfaceColliderVertices;
float smoothingCos = math.cos(math.radians(normalSmoothingAngle));

int totalVerts = renderVertices.Length;

for (int v = 0; v < totalVerts; v++)
{
float3 vertPos = positions[v];
float3 smoothedNormal = finalFaceNormal;

// Iterate ALL brushes to smooth across the entire model
for (int otherBrushIdx = 0; otherBrushIdx < basePolygonCache.Length; otherBrushIdx++)
{
if (!basePolygonCache[otherBrushIdx].IsCreated) continue;
ref var otherSurfaces = ref basePolygonCache[otherBrushIdx].Value.surfaces;
var otherTransform = transformationCache[otherBrushIdx];
var otherNodeToTreeInvTrans = math.transpose(otherTransform.treeToNode);

for(int otherSurf = 0; otherSurf < otherSurfaces.Length; otherSurf++)
{
// Optimization: if we are checking against ourselves (same brush, same surface), skip
// However, we are in a loop over all brushes.
// 'brushNodeOrder' is the index of the current brush in basePolygonCache?
// 'brushIndexOrder.nodeOrder' was used to get current brush.
if (otherBrushIdx == brushNodeOrder && otherSurf == surf) continue;

float4 otherPlaneLocal = otherSurfaces[otherSurf].localPlane;
float4 otherPlaneTree = math.mul(otherNodeToTreeInvTrans, otherPlaneLocal);
float3 otherNormal = otherPlaneTree.xyz;

// If subtractive workflow, we must flip the neighbor normal effectively
// to compare "Inwards vs Inwards" rather than "Inwards vs Outwards"
float3 comparisonNormal = subtractiveWorkflow ? -otherNormal : otherNormal;

// Normal Alignment Check
// This now compares the correctly oriented normals
float dotAngle = math.dot(finalFaceNormal, comparisonNormal);

if (dotAngle < smoothingCos)
continue;

// Plane Distance Check
// distance = dot(N_raw, P) + D_raw.
// We use the raw plane normal and D from the cache for geometric distance.
float dist = math.dot(otherNormal, vertPos) + otherPlaneTree.w;
if (math.abs(dist) < 0.005f)
{
smoothedNormal += comparisonNormal;
}
}
}

var rv = renderVertices[v];
rv.normal = math.normalize(smoothedNormal);
renderVertices[v] = rv;
}
}
}
catch (System.Exception ex) { Debug.LogException(ex); }
}
Expand All @@ -304,15 +386,17 @@ public unsafe void Execute(int index)
var parms = baseSurfaces[surf].destinationParameters;
var UV0 = baseSurfaces[surf].UV0;
var uvMat = math.mul(UV0.ToFloat4x4(), treeToPlane);

// Normals are now correct (flipped/smoothed) before Tangents are calculated
MeshAlgorithms.ComputeUVs(uniqueVertexMapper.surfaceRenderVertices, uvMat);
MeshAlgorithms.ComputeTangents(surfaceIndexList, uniqueVertexMapper.surfaceRenderVertices);

ref var buf = ref surfaceBuffers[surf];
buf.Construct(builder, surfaceIndexList,
uniqueVertexMapper.surfaceColliderVertices,
uniqueVertexMapper.surfaceSelectVertices,
uniqueVertexMapper.surfaceRenderVertices,
surf, flags, parms);
uniqueVertexMapper.surfaceColliderVertices,
uniqueVertexMapper.surfaceSelectVertices,
uniqueVertexMapper.surfaceRenderVertices,
surf, flags, parms);
}

using var queryList = new NativeList<ChiselQuerySurface>(surfaceBuffers.Length, Allocator.Temp);
Expand Down
12 changes: 10 additions & 2 deletions Core/2.Processing/Jobs/JobData/ChiselBrushRenderBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,16 @@ public void Construct(BlobBuilder builder,
this.vertexCount = colliderVertices.Length;
this.indexCount = indices.Length;

// TODO: properly compute hash again, AND USE IT
this.surfaceHashValue = 0;// math.hash(new uint3(normalHash, tangentHash, uv0Hash));
uint surfaceHash = 0;
for (int i = 0; i < renderVertices.Length; i++)
{
var renderVertex = renderVertices[i];
surfaceHash = math.hash(new uint2(surfaceHash, math.hash(renderVertex.normal)));
surfaceHash = math.hash(new uint2(surfaceHash, math.hash(renderVertex.tangent)));
surfaceHash = math.hash(new uint2(surfaceHash, math.hash(renderVertex.uv0)));
}

this.surfaceHashValue = surfaceHash;
this.geometryHashValue = geometryHashValue;

this.aabb = colliderVertices.GetMinMax();
Expand Down
Loading