Skip to content
Merged
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
3 changes: 3 additions & 0 deletions Documentation/Tools.meta

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

73 changes: 73 additions & 0 deletions Documentation/Tools/SnappingTool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Snapping Tool (Unity Editor)

A lightweight editor tool for snapping modular objects together via **SnappingPoints**.

---

## Overview
The **Snapping Tool** helps align modular objects in the Unity SceneView by snapping compatible points together with visual feedback.

- Toggle tool with **`S`** key.
- Drag **SnappingPoints** to nearby compatible points.
- Bezier lines show possible snap targets (white = near, green = snap-ready).
- Release **LMB** to snap and align automatically.

---

## Setup

1. **Add Components**
![SnapPoint.png](../Images/SnapPoint.png)
- Attach `SnappingObject` to your modular prefab or GameObject.
- Add one or more `SnappingPoint` children.
- Set each point’s:
- **SnapType:** `Plug`, `Slot`, or `None`
- **GroupId:** to match compatible types
- **Parent:** assigned automatically (used to prevent self-snapping)

2. **Activate Tool**
- Select a `SnappingObject` in the scene.
- Press **`S`** to toggle the **Snapping Tool**.
- If no object is selected, pressing `S` restores your previous tool.

3. **Use**
- Drag a `Plug` point in SceneView.
- Hover near a compatible `Slot` (white line = search range, green line = snap range).
- Release mouse to snap and align the objects.

![snapping.gif](../Images/snapping.gif)

---

## Snap Rules

A `Plug` point will snap to a `Slot` when:
- They have the **same GroupId**.
- They belong to **different parents**.
- The target point’s **SnapType** is not `Plug`.

---

## Constants

| Constant | Default | Description |
|-----------|----------|-------------|
| `RADIUS_SNAP` | 0.3 | Snap threshold |
| `RADIUS_SEARCH` | 1.0 | Search radius |
| `SNAP_TANGENT_LENGTH` | 1.0 | Bezier curve tangent scale |

---

## Tips
- Only **Plugs** can be dragged to snap.
- Keep `GroupId`s consistent between matching parts.
- Make sure your project includes `Transform.GetMatrix/SetMatrix(ignoreScale: true)` helpers.

---

**Shortcut:** `S`
**Namespace:** `OC.Editor`
**Tool Name:** *Snapping*
**Icon:** `d_Cubemap Icon`

---
3 changes: 3 additions & 0 deletions Documentation/Tools/SnappingTool.md.meta

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

2 changes: 1 addition & 1 deletion Editor/Scripts/SceneInteractionTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using UnityEngine;
using UnityEngine.EventSystems;

namespace OC
namespace OC.Editor
{
[EditorTool("Scene Interaction")]
[Icon(ICON)]
Expand Down
File renamed without changes.
148 changes: 148 additions & 0 deletions Editor/Scripts/Snapping/SnappingTool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.EditorTools;
using UnityEditor.ShortcutManagement;
using UnityEngine;

namespace OC.Editor
{
[EditorTool("Snapping", typeof(SnappingObject))]
[Icon(ICON)]
public class SnappingTool : EditorTool
{
private const string ICON = "d_Cubemap Icon";
private const float RADIUS_SNAP = 0.3f;
private const float RADIUS_SEARCH = 1f;
private const float SNAP_TANGENT_LENGTH = 1f;
private List<SnappingPoint> _sceneSnappingPoints;
private SnappingPoint _nearestSnappingPoint;
private int _selectedPointIndex;

[Shortcut("Snapping Tool", typeof(SceneView), KeyCode.S)]
private static void SnappingToolShortcut()
{
if (Selection.GetFiltered<SnappingObject>(SelectionMode.TopLevel).Length > 0)
{
ToolManager.SetActiveTool(typeof(SnappingTool));
}
else
{
ToolManager.RestorePreviousTool();
}
}

public override void OnActivated()
{
_sceneSnappingPoints = FindObjectsOfType<SnappingPoint>().ToList();
}

public override void OnWillBeDeactivated()
{
_sceneSnappingPoints?.Clear();
}

public override void OnToolGUI(EditorWindow window)
{
var snappingObject = target as SnappingObject;
if (snappingObject == null) return;

if (GUIUtility.hotControl == 0)
{
_selectedPointIndex = -1;
_nearestSnappingPoint = null;
}

if (_selectedPointIndex > -1 && _nearestSnappingPoint != null)
{
var handleSize = HandleUtility.GetHandleSize(snappingObject.Points[_selectedPointIndex].transform.position);
var distance = Vector3.Distance(snappingObject.Points[_selectedPointIndex].transform.position, _nearestSnappingPoint.transform.position);
var scaledDistance = distance * handleSize;

if (scaledDistance < RADIUS_SNAP)
{
DrawHandleBezier(snappingObject.Points[_selectedPointIndex].transform, _nearestSnappingPoint.transform, Color.green);
}
else if (scaledDistance < RADIUS_SEARCH)
{
DrawHandleBezier(snappingObject.Points[_selectedPointIndex].transform, _nearestSnappingPoint.transform, Color.white);
}

var e = Event.current;
if (e.type == EventType.MouseUp && e.button == 0)
{
if (scaledDistance > RADIUS_SNAP) return;
var targetMatrix = Matrix4x4.TRS(
_nearestSnappingPoint.transform.position,
_nearestSnappingPoint.transform.rotation * Quaternion.AngleAxis(180, Vector3.up),
Vector3.one);
SetSnappingObjectMatrix(snappingObject, snappingObject.Points[_selectedPointIndex], targetMatrix);
e.Use();

//Reset state
_selectedPointIndex = -1;
_nearestSnappingPoint = null;
GUIUtility.hotControl = 0;
}
}

for (var i = 0; i < snappingObject.Points.Count; i++)
{
var point = snappingObject.Points[i];
if (point.SnapType is SnappingPoint.Type.Slot or SnappingPoint.Type.None) continue;

EditorGUI.BeginChangeCheck();
var targetPosition = Handles.PositionHandle(point.transform.position, point.transform.rotation);
var targetMatrix = Matrix4x4.TRS(targetPosition, point.transform.rotation, Vector3.one);

if (EditorGUI.EndChangeCheck())
{
_selectedPointIndex = i;
if (TryFindNearestSnappingPoint(point, RADIUS_SEARCH * HandleUtility.GetHandleSize(point.transform.position), out var snappingPoint))
{
_nearestSnappingPoint = snappingPoint;
}

SetSnappingObjectMatrix(snappingObject, point, targetMatrix);
}
}
}

private void DrawHandleBezier(Transform from, Transform to, Color color)
{
var distance = Vector3.Distance(from.position, to.position);
var tangent = distance * 0.3f * SNAP_TANGENT_LENGTH;
var fromTangent = from.position + from.forward * tangent;
var toTangent = to.position + to.forward * tangent;
Handles.DrawBezier(from.position, to.position, fromTangent, toTangent, color, null, 5);
}

private bool TryFindNearestSnappingPoint(SnappingPoint dragged, float radius, out SnappingPoint nearestSnappingPoint)
{
var distance = radius;
nearestSnappingPoint = null;

foreach (var snappingPoint in _sceneSnappingPoints)
{
if (snappingPoint.Parent == dragged.Parent) continue;
if (snappingPoint.GroupId != dragged.GroupId) continue;
if (snappingPoint.SnapType == SnappingPoint.Type.Plug) continue;
var d = Vector3.Distance(snappingPoint.transform.position, dragged.transform.position);
if (!(d < distance)) continue;
distance = d;
nearestSnappingPoint = snappingPoint;
}

return nearestSnappingPoint != null;
}

private void SetSnappingObjectMatrix(SnappingObject snappingObject, SnappingPoint point, Matrix4x4 worldMatrix)
{
if (snappingObject == null) return;
var root = snappingObject.transform.GetMatrix(ignoreScale: true);
var offset = point.transform.GetMatrix(ignoreScale: true).inverse * root;
snappingObject.transform.SetMatrix(worldMatrix * offset);
Undo.RecordObject(snappingObject.transform, "Move SnappingObject");
}
}
}
3 changes: 3 additions & 0 deletions Editor/Scripts/Snapping/SnappingTool.cs.meta

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

81 changes: 0 additions & 81 deletions Editor/Scripts/SnappingTool/SnappingHandlesEditor.cs

This file was deleted.

3 changes: 0 additions & 3 deletions Editor/Scripts/SnappingTool/SnappingHandlesEditor.cs.meta

This file was deleted.

33 changes: 1 addition & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1042,41 +1042,10 @@ You can call this function from the Material Flow component’s context menu in
![BoundBoxColliderSize_Inspector.png](Documentation/Images/BoundBoxColliderSize_Inspector.png)


# Snapping Tool

The Snapping Tool included in the package allows to snap GameObjects to other GameObjects on defined snap points. This functionality is used to align GameObjects in a scene with precision.

## Usage

### SnapPoint Class
The `SnapPoint` class is used to define snapping points on a game object. It is visualized by a blue sphere gizmo.

![SnapPoint Component](Documentation/Images/SnapPoint.png)

- **Group Id**: Only SnapPoints with the same *Group Id* can snap to each other
- **Parent**: The *Parent* property must be set to the GameObject that should move with the snapping points.
- **Type**: Possible Types: *Plug* or *Slot*. Snap Points of *Plug* type can be dragged and snap to *Slot* Snap Points. *Slot* Snap Points can not be dragged.
- **Draw Gizmos**, **Gizmos Color**, **Gizmos Radius**: controls the Gizmos

### SnappingHandles Object
Provides visual handles for all `SnapPoint` components in the GameObjects children.
When the scene window is active and the GameObject containing the `SnappingHandles` component is selected, press the **S-Key** to display the handles. These handles can be used to drag the GameObject and snap it to other snap points if they are within proximity.

![SnappingHandles Component](Documentation/Images/SnappingHandles.png)

## Workflow

1. Attach the `SnapPoint` class to a GameObject to define a snap point.
2. Ensure that the *Parent* property of each `SnapPoint` is set to the GameObject that should be moved when dragging the SnapPoint.
3. Attach the `SnappingHandles` object to the GameObject that should be moved.
4. In the scene window, select the GameObject with the `SnappingHandles` component.
5. Press the **S-Key** to display the snapping handles.
6. Use the handles to drag the GameObject and snap it to other `SnapPoints`.
# [SnappingTool](Documentation/Tools/SnappingTool.md)

![Snapping in action](Documentation/Images/snapping.gif)



# Contributing

We welcome contributions from everyone and appreciate your effort to improve this project.
Expand Down
Loading