- Home /
How do I snap an object to a circular grid
This is how I generate the grid, I would like to know how to find nearest point on the grid to the mouse click position
for (int layer = 1; layer < Layers; layer++)
{
for (int slice = 0; slice < Slices; slice++)
{
float angle = slice * Mathf.PI * 2 / Slices;
Vector3 pos = new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * (gridRadius * layer);
currentPos = transform.position;
desiredPos = new Vector3(currentPos.x + pos.x, currentPos.y + pos.y, currentPos.z + pos.z);
Gizmos.DrawSphere(desiredPos, 1.0f);
}
}
This is how my Grid looks like after I generate it:
What I want is that where ever I click my mouse, lets say to place a simple cube object, I want it to find the nearest point on the grid and place the object there, something like that:
private Grid grid;
private void Awake()
{
grid = FindObjectOfType<Grid>();
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
RaycastHit hitInfo;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hitInfo))
{
PlaceCubeNear(hitInfo.point);
}
}
}
private void PlaceCubeNear(Vector3 clickPoint)
{
//get closest point on grid and CreatePrimitive(PrimitiveType.Cube) at that location
}
You could always do an overlapping sphere or box, find the grid pieces nearest, and snap the object to the nearest grid position.
find the grid pieces nearest, and snap the object to the nearest grid position
this is my problem, not sure how to do that...(or I didn't understand your answer)
Answer by ProtoTerminator · Mar 08, 2018 at 05:20 PM
First, fill a kd-tree (or similar) with all your points for a quicker lookup. Then, make a Plane
and do a Plane.Raycast
to find the place on the plane (the ground, or a wall) that you clicked, and pass that point into the kd-tree's find nearest function to get your result.
Alternatively (maybe faster and easier), using the same plane raycast, check how far the point is from the center and snap that distance to the nearest ring. Then find the nearest snap-angle from there and calculate where the point is on that angle. Or loop through all the points in that ring to find the nearest (slower).
@ProtoTer$$anonymous$$ator Thanks for your comment, I've learned 2 new subjects from it, planes and kd trees.
I've tried to implement this solution but I think I have an issue on the plane as when I use plane.Raycast it seems that I never hit the plane and never get the hitPoint Here is what I did: Declaring the plane the following:
DistanceFromCamera = new Vector3(Camera.main.transform.position.x, Camera.main.transform.position.y, Camera.main.transform.position.z - m_DistanceZ);
plane = new Plane(Vector3.forward, DistanceFromCamera);
and here try to use it as following:
if (Input.Get$$anonymous$$ouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
float enter = 0.0f;
if (plane.Raycast(ray, out enter))
{
Vector3 hitPoint = ray.GetPoint(enter);
PlaceCubeNear(hitPoint);
}
}
Any ideas what am I doing wrong ?
Well, first of all you use Vector3.forward (the worldspace z axis) as your plane normal vector. Since in your screenshot your desired plane seems to be the x-z-plane your normal vector should be Vector3.up.
Second you pass in the distance from the camera as the plane's "d" value which doesn't make too much sense here. You want the distance of the plane from the origin. It's generally easier to just use the plane constructor that takes a normal and a point on the plane.
The ray you get from "ScreenPointToRay" is a worldspace ray we want our plane to be defined in the world space coordinate system. So we use Vector3.up as normal and a worldspace position that is on the plane.
If you want the plane to "stick" to a gameobject just pass the gameobjects transform.up
as normal and transform.position as point on the plane. Of course keep in $$anonymous$$d if the object moves or rotates you have to recreate the plane. Though the Plane is just a struct like a Vector4 so it's actually very cheap.
managed to fix the issue, I didn't initialized the tree list on the start method (just on the OnDrawGizmos) also I dropped the plane usage and did it like this :
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
Debug.Log(hit.point);
if (hit.collider.gameObject.tag == "Terrain")
{
PlaceCubeNear(hit.point);
}
}
Thanks a lot !
Answer by Bunny83 · Mar 11, 2018 at 12:23 PM
If your desired gridpoints follow a simple formula you can simply convert your incoming point on the plane into polar coordinates (angle + distance), clamp both to your desired grid and just convert them back to cartesian coordinates.
The first thing you might want to do is reduce the 3d points to 2d points. The easiest way to do this is to convert them from worldspace into localspace coordinate of an empty gameobject. This gameobject should represent the center of your plane. Just use InverseTransformPoint to get a given worldspace point in local coordinates. Now we can just ignore the y component and only use x and z.
With Mathf.Atan2 you can calculate the angle (in radians) of the given local point in relation to the x axis. The magnitude of your local vector is your distance. Now just clamp those two values as you like. In your picture you splitted the 360° (2*PI) into 20 steps. So you would want to make the angle be a multiple of 360/20== 18 degree (2*PI / 20 == PI/10 radians). Your distance may be clamped to a multiple of 1 or whatever spacing you want.
When done you can use Sin and Cos to get back an actual local space vector and if you need the position in worldspace just use TransformPoint on it.
public static Vector3 ClampCircle(Vector3 aLocalPoint, int aAngleSubDiv, float aDistance)
{
float segment = Mathf.PI*2f / aAngleSubDiv;
float angle = Mathf.Atan2(aLocalPoint.z, aLocalPoint.x);
float dist = aLocalPoint.magnitude;
// clamp
angle = Mathf.Round(angle / segment) * segment;
dist = Mathf.Round(dist / aDistance) * aDistance;
return new Vector3(Mathf.Cos(angle) * dist, 0, Mathf.Sin(angle) * dist);
}
To get the closest point on a plane "attached" to the gameobject depending on where the user clicks, just do this:
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Plane p = new Plane(transform.up, transform.position);
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
float dist;
if (p.Raycast(ray, out dist))
{
var v = transform.InverseTransformPoint(ray.GetPoint(dist));
v = ClampCircle(v, 20, 1f);
v = transform.TransformPoint(v);
// v is the clamped worldspace point
}
}
}
Your answer
Follow this Question
Related Questions
mouse position percentage problem 0 Answers
Hexagon-Grid Distance 2 Answers
Grid-based building system not working 1 Answer
Hexagon Grid 1 Answer
How can I calculate a list of points where a line intersects a grid? 1 Answer