504 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			504 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
|   | /******************************************************************************/ | ||
|  | /* | ||
|  |   Project   - MudBun | ||
|  |   Publisher - Long Bunny Labs | ||
|  |               http://LongBunnyLabs.com | ||
|  |   Author    - Ming-Lun "Allen" Chou | ||
|  |               http://AllenChou.net | ||
|  | */ | ||
|  | /******************************************************************************/ | ||
|  | 
 | ||
|  | #pragma kernel dual_contouring_move_point | ||
|  | #pragma kernel dual_contouring_move_point_2d | ||
|  | 
 | ||
|  | #include "../../Shader/ComputeCommon.cginc" | ||
|  | 
 | ||
|  | #include "../../Shader/BrushFuncs.cginc" | ||
|  | #include "../../Shader/DualMeshingFuncs.cginc" | ||
|  | #include "../../Shader/GenPointDefs.cginc" | ||
|  | #include "../../Shader/IndirectArgsDefs.cginc" | ||
|  | #include "../../Shader/Math/MathConst.cginc" | ||
|  | #include "../../Shader/MeshingModeDefs.cginc" | ||
|  | #include "../../Shader/NormalFuncs.cginc" | ||
|  | #include "../../Shader/RenderModeDefs.cginc" | ||
|  | 
 | ||
|  | // https://www.boristhebrave.com/2018/04/15/dual-contouring-tutorial/ | ||
|  | // https://www.mattkeeter.com/projects/contours/ | ||
|  | 
 | ||
|  | float dualContouringDualQuadsBlend; | ||
|  | float dualContouringRelaxation; | ||
|  | int dualContouringSolverIterations; | ||
|  | int dualContouringBinarySearchIterations; | ||
|  | int dualContouringGradientDescentIterations; | ||
|  | float dualContouringGradientDescentFactor; | ||
|  | 
 | ||
|  | static int aEdgeVertIndex3d[12][2] =  | ||
|  | { | ||
|  |   { 0, 1 },  | ||
|  |   { 1, 5 },  | ||
|  |   { 5, 4 },  | ||
|  |   { 4, 0 },  | ||
|  |   { 2, 3 },  | ||
|  |   { 3, 7 },  | ||
|  |   { 7, 6 },  | ||
|  |   { 6, 2 },  | ||
|  |   { 0, 2 },  | ||
|  |   { 1, 3 },  | ||
|  |   { 5, 7 },  | ||
|  |   { 4, 6 },  | ||
|  | }; | ||
|  | 
 | ||
|  | static int aEdgeVertIndex2d[4][2] =  | ||
|  | { | ||
|  |   { 0, 1 }, | ||
|  |   { 1, 2 }, | ||
|  |   { 2, 3 }, | ||
|  |   { 3, 0 }, | ||
|  | }; | ||
|  | 
 | ||
|  | [numthreads(kThreadGroupSize, 1, 1)] | ||
|  | void dual_contouring_move_point(int3 id : SV_DispatchThreadID) | ||
|  | { | ||
|  | #if defined(MUDBUN_DISABLE_DUAL_CONTOURING) || defined(MUDBUN_FAST_ITERATION) | ||
|  |   return; | ||
|  | #endif | ||
|  | 
 | ||
|  |   if (id.x >= indirectDrawArgs[0]) | ||
|  |     return; | ||
|  | 
 | ||
|  |   int iGenPoint = id.x; | ||
|  | 
 | ||
|  |   if (dualContouringDualQuadsBlend >= 1.0f) | ||
|  |   { | ||
|  |     if (renderMode == kRenderModeQuadSplats) | ||
|  |       aGenPoint[iGenPoint].material.size *= 0.70711f; | ||
|  |     return; | ||
|  |   } | ||
|  | 
 | ||
|  |   float h = 0.5f * voxelSize; | ||
|  |   float3 center = aGenPoint[iGenPoint].posNorm.xyz; | ||
|  |   float3 minCornerOffset = -h; | ||
|  |   int iBrushMask = aGenPoint[iGenPoint].iBrushMask; | ||
|  |    | ||
|  |   float3 aCornerOffset[8] =  | ||
|  |   { | ||
|  |     float3(-h, -h, -h),  | ||
|  |     float3( h, -h, -h),  | ||
|  |     float3(-h,  h, -h),  | ||
|  |     float3( h,  h, -h),  | ||
|  |     float3(-h, -h,  h),  | ||
|  |     float3( h, -h,  h),  | ||
|  |     float3(-h,  h,  h),  | ||
|  |     float3( h,  h,  h),  | ||
|  |   }; | ||
|  | 
 | ||
|  |   float aCornerRes[8]; | ||
|  |   SdfBrushMaterial mat; | ||
|  |   [loop] for (int iCorner = 0; iCorner < 8; ++iCorner) | ||
|  |   { | ||
|  |     aCornerRes[iCorner] = sdf_masked_brushes(center + aCornerOffset[iCorner], iBrushMask, mat); | ||
|  |   } | ||
|  | 
 | ||
|  |   float3 avgEdgeOffset = 0.0f; | ||
|  |   int numEdges = 0; | ||
|  |   float3 aEdgeOffset[12]; | ||
|  |   [loop] for (int iEdge = 0; iEdge < 12; ++iEdge) | ||
|  |   { | ||
|  |     int iVert0 = aEdgeVertIndex3d[iEdge][0]; | ||
|  |     int iVert1 = aEdgeVertIndex3d[iEdge][1]; | ||
|  |     float res0 = aCornerRes[iVert0]; | ||
|  |     float res1 = aCornerRes[iVert1]; | ||
|  |     if (res0 * res1 > 0) | ||
|  |       continue; | ||
|  | 
 | ||
|  |     float3 offset0 = aCornerOffset[iVert0]; | ||
|  |     float3 offset1 = aCornerOffset[iVert1]; | ||
|  | 
 | ||
|  |     int iEdgeOutput = numEdges++; | ||
|  | 
 | ||
|  |     float3 edgeOffset; | ||
|  |     if (res0 == 0.0f && res1 == 0.0f) | ||
|  |     { | ||
|  |       edgeOffset = 0.5f * (offset0 + offset1); | ||
|  |     } | ||
|  |     else if (res0 == 0.0f) | ||
|  |     { | ||
|  |       edgeOffset = offset0; | ||
|  |     } | ||
|  |     else if (res1 == 0.0f) | ||
|  |     { | ||
|  |       edgeOffset = offset1; | ||
|  |     } | ||
|  |     else if (dualContouringBinarySearchIterations <= 0) | ||
|  |     { | ||
|  |       // lerp approximation | ||
|  |       float t = -res0 / (res1 - res0); | ||
|  |       edgeOffset = lerp(offset0, offset1, t); | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |       // binary search | ||
|  |       edgeOffset = 0.5f * (offset0 + offset1); | ||
|  |       [loop] for (int iSearch = 0; iSearch < dualContouringBinarySearchIterations; ++iSearch) | ||
|  |       { | ||
|  |         float resT = sdf_masked_brushes(center + edgeOffset, iBrushMask, mat); | ||
|  |         if (res0 * resT < 0.0f) | ||
|  |         { | ||
|  |           res1 = resT; | ||
|  |           offset1 = edgeOffset; | ||
|  |         } | ||
|  |         else if (resT * res1 < 0.0f) | ||
|  |         { | ||
|  |           res0 = resT; | ||
|  |           offset0 = edgeOffset; | ||
|  |         } | ||
|  |         edgeOffset = 0.5f * (offset0 + offset1); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     avgEdgeOffset += edgeOffset; | ||
|  |     aEdgeOffset[iEdgeOutput] = edgeOffset; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (numEdges <= 0) | ||
|  |     return; | ||
|  | 
 | ||
|  |   avgEdgeOffset /= numEdges; | ||
|  | 
 | ||
|  |   float3 aEdgeNorm[12]; | ||
|  |   [loop] for (int iEdgeOffset = 0; iEdgeOffset < numEdges; ++iEdgeOffset) | ||
|  |   { | ||
|  |     SDF_NORMAL_FULL(aEdgeNorm[iEdgeOffset], center + aEdgeOffset[iEdgeOffset], sdf_masked_brushes, iBrushMask, 1e-2f * voxelSize); | ||
|  |   } | ||
|  | 
 | ||
|  |   float3 bestOffset = 0.0f; | ||
|  | 
 | ||
|  |   if (dualContouringSolverIterations > 0) | ||
|  |   { | ||
|  |     float aD[12]; | ||
|  |     for (int iEdgeOffset = 0; iEdgeOffset < numEdges; ++iEdgeOffset) | ||
|  |     { | ||
|  |       aD[iEdgeOffset] = dot(aEdgeNorm[iEdgeOffset], aEdgeOffset[iEdgeOffset]); | ||
|  |     } | ||
|  |     for (int iSolve = 0; iSolve < dualContouringSolverIterations; ++iSolve) | ||
|  |     { | ||
|  |       for (int iEdgeOffset = 0; iEdgeOffset < numEdges; ++iEdgeOffset) | ||
|  |       { | ||
|  |         bestOffset -= 0.75f * aEdgeNorm[iEdgeOffset] * dot(aEdgeNorm[iEdgeOffset], bestOffset - aEdgeOffset[iEdgeOffset]); | ||
|  |       } | ||
|  |     } | ||
|  |     bestOffset = clamp(bestOffset, -h, h); | ||
|  |   } | ||
|  |   else | ||
|  |   { | ||
|  |     // minimize ||A * x - b||^2 | ||
|  |     float3x3 A[5]; | ||
|  |     float3 b[5]; | ||
|  |     for (int i = 0; i < 5; ++i) | ||
|  |     { | ||
|  |       A[i] = 0.0f; | ||
|  |       b[i] = 0.0f; | ||
|  |     } | ||
|  |     { | ||
|  |       int r = 0; | ||
|  |       float dualContouringRelaxationComp = 1.0f - dualContouringRelaxation; | ||
|  |       while (r < numEdges) | ||
|  |       { | ||
|  |         uint i = uint(r) / 3; | ||
|  |         switch (uint(r) % 3) | ||
|  |         { | ||
|  |           case 0: | ||
|  |             A[i]._m00_m01_m02 = dualContouringRelaxationComp * aEdgeNorm[r]; | ||
|  |             b[i].x = dualContouringRelaxationComp * dot(aEdgeNorm[r], aEdgeOffset[r]); | ||
|  |             break; | ||
|  |           case 1: | ||
|  |             A[i]._m10_m11_m12 = dualContouringRelaxationComp * aEdgeNorm[r]; | ||
|  |             b[i].y = dualContouringRelaxationComp * dot(aEdgeNorm[r], aEdgeOffset[r]); | ||
|  |             break; | ||
|  |           case 2: | ||
|  |             A[i]._m20_m21_m22 = dualContouringRelaxationComp * aEdgeNorm[r]; | ||
|  |             b[i].z = dualContouringRelaxationComp * dot(aEdgeNorm[r], aEdgeOffset[r]); | ||
|  |             break; | ||
|  |         } | ||
|  |         ++r; | ||
|  |       } | ||
|  |       A[4]._m00_m01_m02 = float3(dualContouringRelaxation, 0.0f, 0.0f); | ||
|  |       A[4]._m10_m11_m12 = float3(0.0f, dualContouringRelaxation, 0.0f); | ||
|  |       A[4]._m20_m21_m22 = float3(0.0f, 0.0f, dualContouringRelaxation); | ||
|  |       b[4] = dualContouringRelaxation * avgEdgeOffset; | ||
|  |     } | ||
|  | 
 | ||
|  |     // pseudoinverse | ||
|  |     { | ||
|  |       float3x3 pInvA[5]; | ||
|  |       { | ||
|  |         // tempA = (A' * A)^-1 | ||
|  |         float3x3 tempA = 0.0f; | ||
|  |         for (int iA = 0; iA < 5; ++iA) | ||
|  |         { | ||
|  |           tempA += mul(transpose(A[iA]), A[iA]); | ||
|  |         } | ||
|  |         float3x3 tempACopy = tempA; | ||
|  |         float det =  | ||
|  |           tempACopy._m00 * (tempACopy._m11 * tempACopy._m22 - tempACopy._m21 * tempACopy._m12) -  | ||
|  |           tempACopy._m01 * (tempACopy._m10 * tempACopy._m22 - tempACopy._m12 * tempACopy._m20) +  | ||
|  |           tempACopy._m02 * (tempACopy._m10 * tempACopy._m21 - tempACopy._m11 * tempACopy._m20); | ||
|  |         float detInv = 1.0f / det; | ||
|  |         tempA._m00 = (tempACopy._m11 * tempACopy._m22 - tempACopy._m21 * tempACopy._m12) * detInv; | ||
|  |         tempA._m01 = (tempACopy._m02 * tempACopy._m21 - tempACopy._m01 * tempACopy._m22) * detInv; | ||
|  |         tempA._m02 = (tempACopy._m01 * tempACopy._m12 - tempACopy._m02 * tempACopy._m11) * detInv; | ||
|  |         tempA._m10 = (tempACopy._m12 * tempACopy._m20 - tempACopy._m10 * tempACopy._m22) * detInv; | ||
|  |         tempA._m11 = (tempACopy._m00 * tempACopy._m22 - tempACopy._m02 * tempACopy._m20) * detInv; | ||
|  |         tempA._m12 = (tempACopy._m10 * tempACopy._m02 - tempACopy._m00 * tempACopy._m12) * detInv; | ||
|  |         tempA._m20 = (tempACopy._m10 * tempACopy._m21 - tempACopy._m20 * tempACopy._m11) * detInv; | ||
|  |         tempA._m21 = (tempACopy._m20 * tempACopy._m01 - tempACopy._m00 * tempACopy._m21) * detInv; | ||
|  |         tempA._m22 = (tempACopy._m00 * tempACopy._m11 - tempACopy._m10 * tempACopy._m01) * detInv; | ||
|  | 
 | ||
|  |         // pInvA = (A' * A)^-1 * A' = tempA * A' | ||
|  |         for (i = 0; i < 5; ++i) | ||
|  |         { | ||
|  |           pInvA[i] = mul(tempA, transpose(A[i])); | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       // bestOffset = pInvA * b | ||
|  |       for (int iA = 0; iA < 5; ++iA) | ||
|  |       { | ||
|  |         bestOffset += mul(pInvA[iA], b[iA]); | ||
|  |       } | ||
|  |     } // end: pseudoinverse | ||
|  |   } | ||
|  | 
 | ||
|  |   float3 bestPos = center + bestOffset; | ||
|  | 
 | ||
|  |   // doesn't buy us much and bloats compile time | ||
|  |   // gradient descent | ||
|  |   if (dualContouringGradientDescentIterations > 0) | ||
|  |   { | ||
|  |     float3 n; | ||
|  |     SDF_NORMAL_FULL(n, bestPos, sdf_masked_brushes, iBrushMask, 1e-2f * voxelSize); | ||
|  |     SdfBrushMaterial mat; | ||
|  |     [loop] for (int iDescent = 0; iDescent < dualContouringGradientDescentIterations; ++iDescent) | ||
|  |     { | ||
|  |       float d = sdf_masked_brushes(bestPos, iBrushMask, mat); | ||
|  |       bestOffset -= dualContouringGradientDescentFactor * n * d; | ||
|  |       bestPos = center + bestOffset; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   aGenPoint[iGenPoint].posNorm.xyz = lerp(bestPos, aGenPoint[iGenPoint].posNorm.xyz, dualContouringDualQuadsBlend); | ||
|  | 
 | ||
|  |   if (renderMode == kRenderModeQuadSplats) | ||
|  |     aGenPoint[iGenPoint].material.size *= 1.0f - (1.f - 0.70711f) * dualContouringDualQuadsBlend; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | [numthreads(kThreadGroupSize, 1, 1)] | ||
|  | void dual_contouring_move_point_2d(int3 id : SV_DispatchThreadID) | ||
|  | { | ||
|  | #if defined(MUDBUN_DISABLE_DUAL_CONTOURING) || defined(MUDBUN_FAST_ITERATION) | ||
|  |   return; | ||
|  | #endif | ||
|  | 
 | ||
|  |   if (id.x >= indirectDrawArgs[0]) | ||
|  |     return; | ||
|  | 
 | ||
|  |   int iGenPoint = id.x; | ||
|  | 
 | ||
|  |   if (dualContouringDualQuadsBlend >= 1.0f) | ||
|  |   { | ||
|  |     if (renderMode == kRenderModeQuadSplats) | ||
|  |       aGenPoint[iGenPoint].material.size *= 0.70711f; | ||
|  |     return; | ||
|  |   } | ||
|  | 
 | ||
|  |   float h = 0.5f * voxelSize; | ||
|  |   float3 center = aGenPoint[iGenPoint].posNorm.xyz; | ||
|  |   float3 minCornerOffset = -h; | ||
|  |   int iBrushMask = aGenPoint[iGenPoint].iBrushMask; | ||
|  |    | ||
|  |   float3 aCornerOffset[4] =  | ||
|  |   { | ||
|  |     float3(-h, -h, 0.0f),  | ||
|  |     float3(-h,  h, 0.0f), | ||
|  |     float3( h,  h, 0.0f), | ||
|  |     float3( h, -h, 0.0f), | ||
|  |   }; | ||
|  | 
 | ||
|  |   bool anyOutside = false; | ||
|  |   float aCornerRes[8]; | ||
|  |   SdfBrushMaterial mat; | ||
|  |   [loop] for (int iCorner = 0; iCorner < 4; ++iCorner) | ||
|  |   { | ||
|  |     float cornerRes = sdf_masked_brushes(center + aCornerOffset[iCorner], iBrushMask, mat); | ||
|  |     aCornerRes[iCorner] = cornerRes; | ||
|  |     if (cornerRes >= 0.0f) | ||
|  |       anyOutside = true; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (!anyOutside) | ||
|  |     return; | ||
|  | 
 | ||
|  |   float3 avgEdgeOffset = 0.0f; | ||
|  |   int numEdges = 0; | ||
|  |   float3 aEdgeOffset[4]; | ||
|  |   [loop] for (int iEdge = 0; iEdge < 4; ++iEdge) | ||
|  |   { | ||
|  |     int iVert0 = aEdgeVertIndex2d[iEdge][0]; | ||
|  |     int iVert1 = aEdgeVertIndex2d[iEdge][1]; | ||
|  |     float res0 = aCornerRes[iVert0]; | ||
|  |     float res1 = aCornerRes[iVert1]; | ||
|  |     if (res0 * res1 > 0) | ||
|  |       continue; | ||
|  | 
 | ||
|  |     float3 offset0 = aCornerOffset[iVert0]; | ||
|  |     float3 offset1 = aCornerOffset[iVert1]; | ||
|  | 
 | ||
|  |     int iEdgeOutput = numEdges++; | ||
|  | 
 | ||
|  |     float3 edgeOffset; | ||
|  |     if (res0 == 0.0f && res1 == 0.0f) | ||
|  |     { | ||
|  |       edgeOffset = 0.5f * (offset0 + offset1); | ||
|  |     } | ||
|  |     else if (res0 == 0.0f) | ||
|  |     { | ||
|  |       edgeOffset = offset0; | ||
|  |     } | ||
|  |     else if (res1 == 0.0f) | ||
|  |     { | ||
|  |       edgeOffset = offset1; | ||
|  |     } | ||
|  |     else if (dualContouringBinarySearchIterations <= 0) | ||
|  |     { | ||
|  |       // lerp approximation | ||
|  |       float t = -res0 / (res1 - res0); | ||
|  |       edgeOffset = lerp(offset0, offset1, t); | ||
|  |     } | ||
|  |     else | ||
|  |     { | ||
|  |       // binary search | ||
|  |       edgeOffset = 0.5f * (offset0 + offset1); | ||
|  |       [loop] for (int iSearch = 0; iSearch < dualContouringBinarySearchIterations; ++iSearch) | ||
|  |       { | ||
|  |         float resT = sdf_masked_brushes(center + edgeOffset, iBrushMask, mat); | ||
|  |         if (res0 * resT < 0.0f) | ||
|  |         { | ||
|  |           res1 = resT; | ||
|  |           offset1 = edgeOffset; | ||
|  |         } | ||
|  |         else if (resT * res1 < 0.0f) | ||
|  |         { | ||
|  |           res0 = resT; | ||
|  |           offset0 = edgeOffset; | ||
|  |         } | ||
|  |         edgeOffset = 0.5f * (offset0 + offset1); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     avgEdgeOffset += edgeOffset; | ||
|  |     aEdgeOffset[iEdgeOutput] = edgeOffset; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (numEdges <= 0) | ||
|  |     return; | ||
|  | 
 | ||
|  |   avgEdgeOffset /= numEdges; | ||
|  | 
 | ||
|  |   float3 aEdgeNorm[4]; | ||
|  |   [loop] for (int iEdgeOffset = 0; iEdgeOffset < numEdges; ++iEdgeOffset) | ||
|  |   { | ||
|  |     SDF_NORMAL_2D(aEdgeNorm[iEdgeOffset], center + aEdgeOffset[iEdgeOffset], sdf_masked_brushes, iBrushMask, 1e-2f * voxelSize); | ||
|  |   } | ||
|  | 
 | ||
|  |   // minimize ||A * x - b||^2 | ||
|  |   float2x2 A[3]; | ||
|  |   float2 b[3]; | ||
|  |   for (int i = 0; i < 3; ++i) | ||
|  |   { | ||
|  |     A[i] = 0.0f; | ||
|  |     b[i] = 0.0f; | ||
|  |   } | ||
|  |   { | ||
|  |     int r = 0; | ||
|  |     float dualContouringRelaxationComp = 1.0f - dualContouringRelaxation; | ||
|  |     while (r < numEdges) | ||
|  |     { | ||
|  |       uint i = uint(r) / 2; | ||
|  |       switch (uint(r) % 2) | ||
|  |       { | ||
|  |         case 0: | ||
|  |           A[i]._m00_m01 = dualContouringRelaxationComp * aEdgeNorm[r].xy; | ||
|  |           b[i].x = dualContouringRelaxationComp * dot(aEdgeNorm[r], aEdgeOffset[r]); | ||
|  |           break; | ||
|  |         case 1: | ||
|  |           A[i]._m10_m11 = dualContouringRelaxationComp * aEdgeNorm[r].xy; | ||
|  |           b[i].y = dualContouringRelaxationComp * dot(aEdgeNorm[r], aEdgeOffset[r]); | ||
|  |           break; | ||
|  |       } | ||
|  |       ++r; | ||
|  |     } | ||
|  |     A[2]._m00_m01 = float2(dualContouringRelaxation, 0.0f); | ||
|  |     A[2]._m10_m11 = float2(0.0f, dualContouringRelaxation); | ||
|  |     b[2] = dualContouringRelaxation * avgEdgeOffset.xy; | ||
|  |   } | ||
|  | 
 | ||
|  |   float2 bestOffset = 0.0f; | ||
|  | 
 | ||
|  |   // pseudoinverse | ||
|  |   { | ||
|  |     float2x2 pInvA[3]; | ||
|  |     { | ||
|  |       // tempA = (A' * A)^-1 | ||
|  |       float2x2 tempA = 0.0f; | ||
|  |       for (int iA = 0; iA < 3; ++iA) | ||
|  |       { | ||
|  |         tempA += mul(transpose(A[iA]), A[iA]); | ||
|  |       } | ||
|  |       float2x2 tempACopy = tempA; | ||
|  |       float det = tempACopy._m00 * tempACopy._m11 - tempACopy._m01 * tempACopy._m10; | ||
|  |       float detInv = 1.0f / det; | ||
|  |       tempA._m00 =  tempACopy._m11 * detInv; | ||
|  |       tempA._m01 = -tempACopy._m01 * detInv; | ||
|  |       tempA._m10 = -tempACopy._m10 * detInv; | ||
|  |       tempA._m11 =  tempACopy._m00 * detInv; | ||
|  | 
 | ||
|  |       // pInvA = (A' * A)^-1 * A' = tempA * A' | ||
|  |       for (i = 0; i < 3; ++i) | ||
|  |       { | ||
|  |         pInvA[i] = mul(tempA, transpose(A[i])); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     // bestOffset = pInvA * b | ||
|  |     for (int iA = 0; iA < 3; ++iA) | ||
|  |     { | ||
|  |       bestOffset += mul(pInvA[iA], b[iA]); | ||
|  |     } | ||
|  |   } // end: pseudoinverse | ||
|  | 
 | ||
|  |   float3 bestPos = center + float3(bestOffset, 0.0f); | ||
|  | 
 | ||
|  |   // doesn't buy us much and bloats compile time | ||
|  |   // gradient descent | ||
|  |   if (dualContouringGradientDescentIterations > 0) | ||
|  |   { | ||
|  |     float3 n; | ||
|  |     SDF_NORMAL_2D(n, bestPos, sdf_masked_brushes, iBrushMask, 1e-2f * voxelSize); | ||
|  |     SdfBrushMaterial mat; | ||
|  |     [loop] for (int iDescent = 0; iDescent < dualContouringGradientDescentIterations; ++iDescent) | ||
|  |     { | ||
|  |       float d = sdf_masked_brushes(bestPos, iBrushMask, mat); | ||
|  |       bestOffset -= 0.5f * dualContouringGradientDescentFactor * n.xy * d; | ||
|  |       bestPos = center + float3(bestOffset, 0.0f); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   aGenPoint[iGenPoint].posNorm.xyz = lerp(bestPos, aGenPoint[iGenPoint].posNorm.xyz, dualContouringDualQuadsBlend); | ||
|  |   aGenPoint[iGenPoint].sdfValue = sdf_masked_brushes(bestPos, iBrushMask, mat) + surfaceShift; | ||
|  | 
 | ||
|  |   if (renderMode == kRenderModeQuadSplats) | ||
|  |     aGenPoint[iGenPoint].material.size *= 1.0f - (1.f - 0.70711f) * dualContouringDualQuadsBlend; | ||
|  | } | ||
|  | 
 |