- Home /
Raycast Without Colliders
Basically, I want to select objects with the left mouse button. My objects do not have and should not have colliders.
Now if they did, selecting them using
if (Physics.Raycast(ray.origin, ray.direction, out hit))
is pretty simple.
How would I go about doing this as my objects don't have colliders?
$$anonymous$$y camera is perspective, but how is that relevant?
if it is orthograpic it is easy to get the bounding rectangle on screen and check if the object is hit (without any colliders)
if your camera was orthographic, then testing for collisions with your objects would be a simple matter of comparing the x,y of the mouse point and then doing either a square or circle (or some other complex shape) collision test. Since you don't have a orthographic camera, you need to take into account the FOV, because depending on depth, the x/y of the mouse point are different.
The basic method is construct a Vector3 that describes the position of the mouse on the screen, then move it forward away from the camera slowly until the vector is inside of one of your objects. There are some optimizations that you can do to this. This is the work that the collider handles.
Answer by Loius · Apr 19, 2013 at 08:46 PM
Put colliders on them anyway. Then set the colliders to a different physics layer and make that layer non-collisive with every layer (including itself) (Edit > Project Settings > Physics). Then use layer-based raycasts to find them. :)
Still, at very least, not a good solution for animated skinned mesh (which is basically any animations with bone, I believe). Supposing you can actually use a mesh collider there (which is possible).
How's that not good? Slam a few non-colliding simple colliders on the bones and voila.
You never* want an animated-mesh-collider in a game environment. It's just ridiculously slow to modify the collision data all the time. I suppose if you're in the realm of simulation where accuracy is more important than time, that would be great.
*mostly never
What if it literally doesn't work to use colliders?
Case in point, I need to raycast a ring, and making a mesh that's a ring means a concave mesh collider, which apparently ignores raycasts, as I've proven through rigorous testing.
$$anonymous$$y only alternative thought was to render to a texture and check the color under the mouse position. I could use unique colors in order to deter$$anonymous$$e which object I'm under. But I'm wary of the cost associated with this option...
Thoughts?
Answer by Benproductions1 · Feb 02, 2013 at 02:34 AM
Look at this Threadpost: http://forum.unity3d.com/threads/14378-Raycast-without-colliders It explains in detail how to implement your own raycasting algorithm, only using triangle information and no colliders.
I believe the thread does require colliders, despite the title.
@Cawas You are wrong. The whole point is that it is a reimplementation of raycasting in Unity
@Benproductions1 you're probably right. Sometimes I don't even know what I wrote.
Answer by drudiverse · Jun 26, 2014 at 08:16 AM
STRANGE SOLUTION: to raycast via pixel color, so you can check if you hit a visible pixel of an object...
Make a small sphere with preferably the same color regardless of lighting and textures, I don't know how that is done, perhaps pink sphere of shaderless unity objects...
If you click with the mouse, then scan a ray in a line from the camera by moving the pink sphere backwards and forwards through space, and see if it gets covered up by a pixel of a different color:
Place sphere 1 meter away... if pixel = pink:
place sphere 1000 meters away, if pixel != pink:
place sphere 500 meters away, if pixel = pink:
place sphere 750 meters away... etc.
In this way you can devide the distance where the collider object is in 2 and slowly narrow down to the precise point in space where the pink object is covered by another object.
if you devide 3000m by 2 many times, it takes 16 steps to find your collision object to within 0.1 meters... so perhaps 16 frames, and say 10 steps to search 200 meters range.
If it is necessary to take 16 frames, i dont know. perhaps it can be done in one frame?
Anyways, when you have the location of the collision pixel, you can go through all the objects on the screen, compare their position, perhaps compare all the triangles on the screen if you want to be very precise. it would be convenient to have a list of triangles in a particular point in space!?
If someone has advice about the above implementation, and ways to refine it or think's it's abit nonsense, I would be very interestested to have it optimised!
for distance precision the pink sphere should be a flat square.
Answer by Xelnath · Aug 29, 2015 at 07:33 PM
I found the solution of "just throw a mesh collider on it" to not be acceptable.
However, it is probably the right call for anything runtime. If you want something for the editor, use this. (Caveats included: This was designed for a 2d system to enable dragging in the 2d plane from a 3d camera view.)
Hopefully, someone will take the information here and distill it into a reusable form:
using UnityEngine;
using UnityEditor;
using System.Collections;
using System;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;
[CustomEditor(typeof(BaseUnit), true)]
public class ClickBaseUnitEditor : Vexe.Editor.Editors.BaseBehaviourEditor
{
public static bool blockingMouseInput = false;
public static Vector3 offset = Vector3.zero;
public const string SuspendedBuff = "_EDITOR_Suspended";
public void SuspendUnit(BaseUnit unit)
{
if ( Application.isPlaying )
{ /// custom stuff
}
}
public void ReleaseUnit(BaseUnit unit)
{/// custom stuff
}
public static Ray GetMouseClickRay()
{
Event e = Event.current;
var camera = SceneView.lastActiveSceneView.camera;
var mousePosition = e.mousePosition;
mousePosition.y = camera.pixelHeight - mousePosition.y;
var ray = camera.ScreenPointToRay(mousePosition);
return ray;
}
public static bool MouseClickRaycheckSpineObjects(out IList<BaseUnit> spineUnitsFound, out IList<Vector3> hits)
{
var ray = GetMouseClickRay();
var spineObjs = Transform.FindObjectsOfType<SkeletonAnimation>();
spineUnitsFound = new List<BaseUnit>();
hits = new List<Vector3>();
//Debug.DrawRay(ray.origin, ray.direction * 80f + new Vector3(0.1f,0f,0f), Color.blue,10);
foreach ( var spineGameObj in spineObjs )
{
var root = spineGameObj.transform.position;
var intersectFound = false;
var meshFilter = spineGameObj.GetComponent<MeshFilter>();
if ( meshFilter != null )
{
var mesh = meshFilter.sharedMesh;
for ( int subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex ++ )
{
var triangles = mesh.GetTriangles(subMeshIndex);
for ( int iTriBegin = 0; iTriBegin < triangles.Length; iTriBegin += 3 )
{
Vector3 a, b, c;
a = mesh.vertices[triangles[iTriBegin]];
b = mesh.vertices[triangles[iTriBegin+1]];
c = mesh.vertices[triangles[iTriBegin+2]];
a = spineGameObj.transform.TransformPoint(a);
b = spineGameObj.transform.TransformPoint(b);
c = spineGameObj.transform.TransformPoint(c);
if ( Intersect(a, b, c, ray) )
{
intersectFound = true;
break;
}
}
if ( intersectFound ) break;
}
}
if ( intersectFound )
{
var baseUnit = spineGameObj.GetComponentInParent<BaseUnit>();
if ( baseUnit )
{
var plane = new Plane( Vector3.back, spineGameObj.transform.position );
Vector3 p = Vector3.zero;
float rayDistance;
if ( plane.Raycast(ray, out rayDistance )) {
p = ray.GetPoint(rayDistance);
}
if ( baseUnit.gameObject == Selection.activeGameObject )
{
spineUnitsFound.Insert(0, baseUnit);
hits.Insert(0, p);
}
else
{
spineUnitsFound.Add(baseUnit);
hits.Add( p);
}
}
}
}
if ( spineUnitsFound.Count > 0 )
return true;
return false;
}
public static bool MouseClickRaycheckBaseUnit(out IList<BaseUnit> unitsFound, out IList<Vector3> hits)
{
var ray = GetMouseClickRay();
var coll = Physics2D.RaycastAll(ray.origin, ray.direction, Mathf.Infinity);
//Debug.DrawRay(ray.origin, ray.direction * 80f + new Vector3(0.1f,0f,0f), Color.blue,10);
unitsFound = new List<BaseUnit>();
hits = new List<Vector3>();
foreach ( var c in coll )
{
var baseUnit = c.collider.gameObject.GetComponentInParent<BaseUnit>();
if ( baseUnit )
{
var plane = new Plane( Vector3.back, baseUnit.transform.position);
Vector3 p = Vector3.zero;
float rayDistance;
if ( plane.Raycast(ray, out rayDistance )) {
p = ray.GetPoint(rayDistance);
}
if ( baseUnit.gameObject == Selection.activeGameObject )
{
unitsFound.Insert(0, baseUnit);
hits.Insert(0, p);
}
else
{
unitsFound.Add(baseUnit);
hits.Add( p);
}
}
}
IList<BaseUnit> spineUnitsFound;
IList<Vector3> spineHits;
var anythingFound = MouseClickRaycheckSpineObjects( out spineUnitsFound, out spineHits );
if ( anythingFound )
{
for ( int i = 0; i < spineUnitsFound.Count; i ++ )
{
var baseUnit = spineUnitsFound[i];
var p = spineHits[i];
if ( unitsFound.Contains(baseUnit) )
continue;
if ( baseUnit.gameObject == Selection.activeGameObject )
{
unitsFound.Insert(0, baseUnit);
hits.Insert(0, p);
}
else
{
unitsFound.Add(baseUnit);
hits.Add( p);
}
}
}
if ( unitsFound.Count > 0 )
return true;
return false;
}
public static bool MouseClickRaycheckAgainstBaseUnitPlane(BaseUnit b, out Vector3 intersect)
{
intersect = Vector3.zero;
var plane = new Plane( Vector3.back, b.transform.position);
/*
var camera = SceneView.lastActiveSceneView.camera;
var mousePosition = Event.current.mousePosition;
mousePosition.y = camera.pixelHeight - mousePosition.y;
var ray2 = camera.ScreenPointToRay(mousePosition);
*/
var ray = GetMouseClickRay();
float rayDistance;
if ( plane.Raycast(ray, out rayDistance )) {
intersect = ray.GetPoint(rayDistance);
return true;
}
return false;
}
private void OnSceneGUI( )
{
Event e = Event.current;
var controlID = GUIUtility.GetControlID(FocusType.Passive);
var type = e.GetTypeForControl(controlID );
var selection = Selection.activeGameObject;
switch ( type )
{
case EventType.MouseDown:
GUIUtility.hotControl = controlID;
//Debug.Log("Mouse Down");
IList<BaseUnit> unitsFound;
IList<Vector3> hits;
var anythingFound = MouseClickRaycheckBaseUnit(out unitsFound, out hits);
if ( anythingFound && unitsFound[0].gameObject == Selection.activeGameObject )
{
Debug.Log("You clicked on:" + unitsFound[0]);
offset = unitsFound[0].transform.position - hits[0];
blockingMouseInput = true;
e.Use();
}
else if ( anythingFound )
{
Selection.activeGameObject = unitsFound[0].gameObject;
}
else
{
Selection.activeObject = null;
}
break;
case EventType.MouseMove:
case EventType.MouseDrag:
if ( blockingMouseInput )
{
if ( selection )
{
var baseUnit = selection.gameObject.GetComponentInParent<BaseUnit>();
Vector3 p;
if ( MouseClickRaycheckAgainstBaseUnitPlane(baseUnit, out p) )
{
//Debug.Log("Dragging to "+p);
baseUnit.gameObject.transform.position = p + offset;
SuspendUnit( baseUnit );
e.Use();
}
//Debug.Log("Mouse " + type + " | " + e.mousePosition);
}
}
break;
case EventType.MouseUp:
GUIUtility.hotControl = 0;
//Debug.Log("Mouse Up");
blockingMouseInput = false;
break;
}
}
/// <summary>
/// Checks if the specified ray hits the triagnlge descibed by p1, p2 and p3.
/// Möller–Trumbore ray-triangle intersection algorithm implementation.
/// </summary>
/// <param name="p1">Vertex 1 of the triangle.</param>
/// <param name="p2">Vertex 2 of the triangle.</param>
/// <param name="p3">Vertex 3 of the triangle.</param>
/// <param name="ray">The ray to test hit for.</param>
/// <returns><c>true</c> when the ray hits the triangle, otherwise <c>false</c></returns>
public static bool Intersect(Vector3 p1, Vector3 p2, Vector3 p3, Ray ray)
{
// Vectors from p1 to p2/p3 (edges)
Vector3 e1, e2;
Vector3 p, q, t;
float det, invDet, u, v;
//Find vectors for two edges sharing vertex/point p1
e1 = p2 - p1;
e2 = p3 - p1;
// calculating determinant
p = Vector3.Cross(ray.direction, e2);
//Calculate determinat
det = Vector3.Dot(e1, p);
//if determinant is near zero, ray lies in plane of triangle otherwise not
if (det > -Mathf.Epsilon && det < Mathf.Epsilon) { return false; }
invDet = 1.0f / det;
//calculate distance from p1 to ray origin
t = ray.origin - p1;
//Calculate u parameter
u = Vector3.Dot(t, p) * invDet;
//Check for ray hit
if (u < 0 || u > 1) { return false; }
//Prepare to test v parameter
q = Vector3.Cross(t, e1);
//Calculate v parameter
v = Vector3.Dot(ray.direction, q) * invDet;
//Check for ray hit
if (v < 0 || u + v > 1) { return false; }
if ((Vector3.Dot(e2, q) * invDet) > Mathf.Epsilon)
{
//ray does intersect
return true;
}
// No hit at all
return false;
}
}
Your answer
Follow this Question
Related Questions
OnCollisionEnter2D is not working 0 Answers
[2D] Raycast to mouse cursor = offset on Y axis 0 Answers
RaycastHit2D hits "itself" 3 Answers
Object movement, collider and Physics. 0 Answers