Skip to content

Commit

Permalink
Mask Volumes tools update (#22)
Browse files Browse the repository at this point in the history
* Fixed the issue when the scene view camera moves on selecting the painting tool.

* Use normalized values for color and opacity settings. Fixed the effect of opacity and hardness.

* Added the brush color inversion while Ctrl is pressed, for quick erasing.

* Replaced the Recreate Asset button with Resample Asset. The data is not lost after resizing or changing voxel density now.

* Fixed a Mask Volume resampling error when old resolution has 1 on any axis.

* Mask Volume: Use resolution from MaskVolumeAsset instead of MaskVolumeArtistParameters for rendering. Resolution in parameters is just for creating new assets or resampling to a new resolution.

* Mask Volume: Use normal bias in painting.

* Mask Volume: Use input pressure as opacity.

* Mask Volume: Added inner radius and normal bias brush gizmos.
  • Loading branch information
AndrewSaraevUnity authored and pastasfuture committed Aug 19, 2021
1 parent aa6d07a commit 04ed570
Show file tree
Hide file tree
Showing 7 changed files with 306 additions and 146 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,25 @@ public class MaskVolumeBrush
const float k_Stepping = 0.5f;
const int k_RaycastBufferSize = 128;

public float Radius = 0.5f;
public float OuterRadius = 0.5f;
public float InnerRadius = 1f;
public float NormalBias = 0f;

public bool MeshCollidersOnly = true;
public LayerMask PhysicsLayerMask = ~0;

bool m_Hovering = false;
Vector3 m_Position;
Vector3 m_Normal;

bool m_Applying = false;
Vector3 m_LastApplyPosition;
Vector3 m_LastApplyNormal;
float m_LastApplyPressure;

public event Action<Vector3> OnApply;
public delegate void Apply(Vector3 position, Vector3 normal, float pressure, bool control);

public event Apply OnApply;
public event Action OnStopApplying;

public void OnSceneGUI(SceneView sceneView)
Expand Down Expand Up @@ -48,7 +56,7 @@ public void OnSceneGUI(SceneView sceneView)
case EventType.MouseDown:
case EventType.MouseDrag:
UpdateBrush(e.mousePosition);
ApplyBrush();
ApplyBrush(e.pressure, e.control);
break;

case EventType.MouseUp:
Expand Down Expand Up @@ -82,13 +90,16 @@ static bool SceneViewInUse(Event e)
void UpdateBrush(Vector2 mousePosition)
{
Ray mouseRay = HandleUtility.GUIPointToWorldRay(mousePosition);
m_Hovering = Raycast(mouseRay, out var hitPosition);
m_Hovering = Raycast(mouseRay, out var hitPosition, out var hitNormal);
if (m_Hovering)
{
m_Position = hitPosition;
m_Normal = hitNormal;
}
}

RaycastHit[] raycastBuffer;
bool Raycast(Ray ray, out Vector3 hitPosition)
bool Raycast(Ray ray, out Vector3 hitPosition, out Vector3 hitNormal)
{
if (MeshCollidersOnly)
{
Expand All @@ -97,21 +108,23 @@ bool Raycast(Ray ray, out Vector3 hitPosition)
var hitCount = Physics.RaycastNonAlloc(ray, raycastBuffer, float.MaxValue, PhysicsLayerMask, QueryTriggerInteraction.Ignore);

var minDistance = float.MaxValue;
var nearestPosition = Vector3.zero;
var nearestHit = -1;

for (int i = 0; i < hitCount; i++)
{
var hit = raycastBuffer[i];
if (hit.collider is MeshCollider && hit.distance < minDistance)
{
minDistance = hit.distance;
nearestPosition = hit.point;
nearestHit = i;
}
}

if (minDistance != float.MaxValue)
if (nearestHit != -1)
{
hitPosition = nearestPosition;
var hit = raycastBuffer[nearestHit];
hitPosition = hit.point;
hitNormal = hit.normal;
return true;
}
}
Expand All @@ -120,41 +133,50 @@ bool Raycast(Ray ray, out Vector3 hitPosition)
if (Physics.Raycast(ray, out var hit, float.MaxValue, PhysicsLayerMask, QueryTriggerInteraction.Ignore))
{
hitPosition = hit.point;
hitNormal = hit.normal;
return true;
}
}

hitPosition = default;
hitNormal = default;
return false;
}

/// <summary>
/// Apply brush.
/// </summary>
void ApplyBrush()
void ApplyBrush(float pressure, bool control)
{
if (m_Hovering)
{
if (!m_Applying)
{
m_Applying = true;
OnApply?.Invoke(m_Position);
OnApply?.Invoke(m_Position, m_Normal, pressure, control);
m_LastApplyPosition = m_Position;
m_LastApplyNormal = m_Normal;
m_LastApplyPressure = pressure;
}
else
{
var moveDistance = Vector3.Distance(m_Position, m_LastApplyPosition);

// If mouse moved too far due to low framerate or high movement speed, fill the gap with more stamps
var maxStep = Radius * k_Stepping;
var maxStep = OuterRadius * k_Stepping;
var steps = (int) (moveDistance / maxStep);

var maxTime = Time.realtimeSinceStartup + k_MaxTimeSpentPerEvent;
var startPosition = m_LastApplyPosition;
var startNormal = m_LastApplyNormal;
var startPressure = m_LastApplyPressure;
for (int i = 1; i <= steps; i++)
{
m_LastApplyPosition = Vector3.Lerp(startPosition, m_Position, (float) i / steps);
OnApply?.Invoke(m_LastApplyPosition);
var time = (float) i / steps;
m_LastApplyPosition = Vector3.Lerp(startPosition, m_Position, time);
m_LastApplyNormal = Vector3.Lerp(startNormal, m_Normal, time);
m_LastApplyPressure = Mathf.Lerp(startPressure, pressure, time);
OnApply?.Invoke(m_LastApplyPosition, m_LastApplyNormal, m_LastApplyPressure, control);
if (Time.realtimeSinceStartup > maxTime)
break;
}
Expand All @@ -165,7 +187,28 @@ void ApplyBrush()
void DrawGizmo(SceneView sceneView)
{
if (m_Hovering)
Handles.DrawWireDisc(m_Position, (sceneView.camera.transform.position - m_Position).normalized, Radius);
{
var oldHandleColor = Handles.color;

var discNormal = (sceneView.camera.transform.position - m_Position).normalized;

if (InnerRadius != 1f)
{
Handles.color = new Color(1f, 1f, 1f, 0.5f);
Handles.DrawWireDisc(m_Position, discNormal, OuterRadius);
}

Handles.color = Color.white;
Handles.DrawWireDisc(m_Position, discNormal, OuterRadius * InnerRadius);

if (NormalBias != 0f)
{
Handles.color = new Color(Mathf.Abs(m_Normal.x), Mathf.Abs(m_Normal.y), Mathf.Abs(m_Normal.z));
Handles.DrawLine(m_Position, m_Position + m_Normal * NormalBias);
}

Handles.color = oldHandleColor;
}
}

public void StopIfApplying()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ internal class MaskVolumeEditor : Editor
static Dictionary<MaskVolume, HierarchicalBox> shapeBoxes = new Dictionary<MaskVolume, HierarchicalBox>();
internal static Dictionary<MaskVolume, HierarchicalBox> blendBoxes = new Dictionary<MaskVolume, HierarchicalBox>();

internal static Color32 BrushColor = new Color32(255, 0, 0, 255);
internal static Color BrushColor = new Color(1f, 0f, 0f, 1f);
internal static bool BrushApplyRed = true;
internal static bool BrushApplyGreen = true;
internal static bool BrushApplyBlue = true;
internal static float BrushHardness = 1f;

SerializedMaskVolume m_SerializedMaskVolume;

Expand Down Expand Up @@ -52,7 +51,12 @@ void OnDisable()
Brush.OnStopApplying -= OnStopApplyingBrush;
Undo.undoRedoPerformed -= OnUndoRedoPerformed;
}


public override bool RequiresConstantRepaint()
{
return EditMode.editMode == k_EditPaint;
}

public override void OnInspectorGUI()
{
serializedObject.Update();
Expand Down Expand Up @@ -119,7 +123,7 @@ protected void OnSceneGUI()
if (!blendBoxes.TryGetValue(maskVolume, out HierarchicalBox blendBox)) { return; }
if (!shapeBoxes.TryGetValue(maskVolume, out HierarchicalBox shapeBox)) { return; }

if (EditMode.editMode != EditMode.SceneViewEditMode.GridPainting)
if (EditMode.editMode != k_EditPaint)
Brush.StopIfApplying();

switch (EditMode.editMode)
Expand Down Expand Up @@ -182,6 +186,7 @@ protected void OnSceneGUI()
break;
case k_EditPaint:
var sceneView = SceneView.currentDrawingSceneView;
Brush.NormalBias = maskVolume.parameters.normalBiasWS;
Brush.OnSceneGUI(sceneView);
sceneView.Repaint();
break;
Expand All @@ -190,7 +195,7 @@ protected void OnSceneGUI()

bool m_ApplyingBrush;

void OnApplyBrush(Vector3 position)
void OnApplyBrush(Vector3 position, Vector3 normal, float pressure, bool control)
{
// TODO: Multi-editing.
var maskVolume = (MaskVolume)target;
Expand All @@ -212,10 +217,10 @@ void OnApplyBrush(Vector3 position)
var maskTransform = maskVolume.transform;
var localToWorld = Matrix4x4.TRS(maskTransform.position, maskTransform.rotation, Vector3.one);
var worldToLocal = localToWorld.inverse;
var localBrushPosition = worldToLocal.MultiplyPoint3x4(position);
var localBrushPosition = worldToLocal.MultiplyPoint3x4(position + normal * parameters.normalBiasWS);

var minAffectedLocalPosition = localBrushPosition - firstVoxelLocalPosition - new Vector3(Brush.Radius, Brush.Radius, Brush.Radius);
var maxAffectedLocalPosition = localBrushPosition - firstVoxelLocalPosition + new Vector3(Brush.Radius, Brush.Radius, Brush.Radius);
var minAffectedLocalPosition = localBrushPosition - firstVoxelLocalPosition - new Vector3(Brush.OuterRadius, Brush.OuterRadius, Brush.OuterRadius);
var maxAffectedLocalPosition = localBrushPosition - firstVoxelLocalPosition + new Vector3(Brush.OuterRadius, Brush.OuterRadius, Brush.OuterRadius);

var minX = Mathf.Max(Mathf.RoundToInt(minAffectedLocalPosition.x / voxelSize.x), 0);
var minY = Mathf.Max(Mathf.RoundToInt(minAffectedLocalPosition.y / voxelSize.y), 0);
Expand All @@ -226,6 +231,17 @@ void OnApplyBrush(Vector3 position)

var dataSHL0 = maskVolumeAsset.payload.dataSHL0;
var strideSHL0 = MaskVolumePayload.GetDataSHL0Stride();
var color32 = (Color32)BrushColor;

// A way of erase quickly
if (control)
{
color32.r = (byte)(255 - color32.r);
color32.g = (byte)(255 - color32.g);
color32.b = (byte)(255 - color32.b);
}

float opacity = BrushColor.a * pressure;

for (int z = minZ; z <= maxZ; z++)
{
Expand All @@ -244,22 +260,20 @@ void OnApplyBrush(Vector3 position)
var longestComponent = Mathf.Max(Mathf.Max(Mathf.Abs(toBrush.x), Mathf.Abs(toBrush.y)), Mathf.Abs(toBrush.z));
var halfVoxelLength = Vector3.Scale(toBrush / longestComponent, voxelSize).magnitude * 0.5f;

var outerRadius = Brush.Radius + halfVoxelLength;
var innerRadius = Brush.Radius - halfVoxelLength;
if (innerRadius > 0f)
innerRadius *= BrushHardness;
var outerRadius = Brush.OuterRadius + halfVoxelLength;
var innerRadius = Brush.OuterRadius * Brush.InnerRadius - halfVoxelLength;

var distanceToBrush = toBrush.magnitude;
var opacity = MaskVolumePayload.ToUNormByte(MaskVolumePayload.FromUNormByte(BrushColor.a) * (outerRadius - distanceToBrush) / (outerRadius - innerRadius));
if (opacity > 0)
color32.a = (byte)(opacity * Mathf.Clamp01((outerRadius - distanceToBrush) / (outerRadius - innerRadius)) * 255f);
if (color32.a > 0)
{
var indexDataBaseSHL0 = i * strideSHL0;
if (BrushApplyRed)
dataSHL0[indexDataBaseSHL0 + 0] = ApplyBrushChannel(dataSHL0[indexDataBaseSHL0 + 0], BrushColor.r, opacity); // shAr.w
dataSHL0[indexDataBaseSHL0 + 0] = ApplyBrushChannel(dataSHL0[indexDataBaseSHL0 + 0], color32.r, color32.a); // shAr.w
if (BrushApplyGreen)
dataSHL0[indexDataBaseSHL0 + 1] = ApplyBrushChannel(dataSHL0[indexDataBaseSHL0 + 1], BrushColor.g, opacity); // shAg.w
dataSHL0[indexDataBaseSHL0 + 1] = ApplyBrushChannel(dataSHL0[indexDataBaseSHL0 + 1], color32.g, color32.a); // shAg.w
if (BrushApplyBlue)
dataSHL0[indexDataBaseSHL0 + 2] = ApplyBrushChannel(dataSHL0[indexDataBaseSHL0 + 2], BrushColor.b, opacity); // shAb.w
dataSHL0[indexDataBaseSHL0 + 2] = ApplyBrushChannel(dataSHL0[indexDataBaseSHL0 + 2], color32.b, color32.a); // shAb.w
}
}
}
Expand All @@ -271,16 +285,10 @@ void OnApplyBrush(Vector3 position)
static byte ApplyBrushChannel(byte value, byte targetValue, byte opacity)
{
var delta = targetValue - value;
if (delta < 0)
{
if (delta < -opacity)
delta = -opacity;
}
else
{
if (delta > opacity)
delta = opacity;
}
if (delta < -opacity)
delta = -opacity;
else if (delta > opacity)
delta = opacity;
return (byte)(value + delta);
}

Expand Down

0 comments on commit 04ed570

Please sign in to comment.