Picking objects

Started by
12 comments, last by Jason Z 11 years ago

I guess you use c#. Here is c# class representing triangle mesh. You can use picking functionality from this code:


/// <summary:
    /// 3D Geometry that consist from indices, vertices, normals, texture coordinates.
    /// </summary>
    public sealed class TriangleMesh
    {
        private static uint s_internalIdCounter;

	    private BoundingSphere m_boundingSphere;
	    private BoundingBox m_boundingBox;

        /// <summary>
        /// Gets an internal ID that value is unique for each instance.
        /// </summary>
        public uint InternalID { get; private set; }

	    /// <summary>
        /// Gets an array of the indices of the geometry.
        /// </summary>
        public ushort[] Indices { get; private set; }

	    /// <summary>
	    /// Gets an array of the vertices of the geometry.
	    /// </summary>
        public Float3[] Vertices { get; private set; }

	    /// <summary>
	    /// Gets an array of the normals of the geometry. Value can be <c>null</c>.
	    /// </summary>
        public Float3[] Normals { get; private set; }

	    /// <summary>
	    /// Gets an array of the normals of the geometry. Value can be <c>null</c>.
	    /// </summary>
        public Float2[] TexCoords { get; private set; }

	    public TriangleMesh(ushort[] indices, Float3[] vertices, Float3[] normals, Float2[] texCoords)
	    {
            if (indices.Length < 3)
                throw new ArgumentException("The length of the indices array shouldn't be less than 3 elements.", "indices");
            if (vertices.Length < 3)
                throw new ArgumentException("The length of the vertices array shouldn't be less than 3 elements.", "vertices");

	        Indices = indices;
	        Vertices = vertices;
	        Normals = normals;
	        TexCoords = texCoords;

	        unchecked
	        {
	            s_internalIdCounter++;
	        }
	        InternalID = s_internalIdCounter;

	        CalculateBounds();
	    }

        /// <summary>
        /// Calculates a bounding box and a bonding sphere of the geometry.
        /// </summary>
	    private void CalculateBounds()
	    {
	        m_boundingBox = BoundingBox.FromPoints(Vertices);
	        m_boundingSphere = BoundingSphere.FromBox(m_boundingBox);
	    }

        /// <summary>
        /// Gets transformed bounding sphere of the geometry.
        /// </summary>
        /// <param name="transform">Transformation matrix.</param>
        /// <returns>Transformed bounding sphere.</returns>
        public BoundingSphere CalculateBoundingSphere(Float4x4 transform)
        {
            Float3 center = Float3.TransformCoordinate(m_boundingSphere.Center, transform);
            return new BoundingSphere(center, m_boundingSphere.Radius);
        }

        /// <summary>
        /// Gets transformed bounding box of the geometry.
        /// </summary>
        /// <param name="transform">Transformation matrix.</param>
        /// <returns>Transformed bounding box.</returns>
        public BoundingBox CalculateBoundingBox(Float4x4 transform)
        {
            Float3 min = Float3.TransformCoordinate(m_boundingBox.Minimum, transform);
            Float3 max = Float3.TransformCoordinate(m_boundingBox.Maximum, transform);
            return new BoundingBox(min, max);
        }

        /// <summary>
        /// Determines whether a ray intersects the geometry.
        /// </summary>
        /// <param name="transform">Transformation matrix of the geometry</param>
        /// <param name="ray">The ray which will be tested for intersection.</param>
        /// <param name="distance">When the method completes, contains the distance at which the ray intersected the plane.</param>
        /// <param name="faceIndex">When the method completes, contains the index of face which the ray intersects.</param>
        /// <returns><c>true</c> if the ray intersects the plane; otherwise, <c>false</c>.</returns>
        public bool Intersects(Float4x4 transform, Ray ray, out float distance, out int faceIndex)
        {
            float u, v;
            return Intersects(transform , ray, out distance, out faceIndex, out u, out v);
        }

        /// <summary>
        /// Determines whether a ray intersects the geometry.
        /// </summary>
        /// <param name="transform">Transformation matrix of the geometry</param>
        /// <param name="ray">The ray which will be tested for intersection.</param>
        /// <param name="distance">When the method completes, contains the distance at which the ray intersected the plane.</param>
        /// <param name="faceIndex">When the method completes, contains the index of face which the ray intersects.</param>
        /// <param name="u">Barycentric U of face which the ray intersects.</param>
        /// <param name="v">Barycentric V of face which the ray intersects.</param>
        /// <returns><c>true</c> if the ray intersects the plane; otherwise, <c>false</c>.</returns>
        public bool Intersects(Float4x4 transform, Ray ray, out float distance, out int faceIndex, out float u, out float v)
        {
            // Convert ray to model space
            Float3 near = ray.Position;
            Float3 dir = ray.Direction;
            transform.Invert();
            Float3 tmp = near;
            Float3.TransformCoordinate(ref tmp, ref transform, out near);
            tmp = dir;
            Float3.TransformNormal(ref tmp, ref transform, out dir);
            Ray modelSpaceRay = new Ray(near, dir);

            // Test bounding sphere first
            BoundingSphere bs = CalculateBoundingSphere(transform);
            if (Ray.Intersects(ray, bs, out distance))
            {
                if (Indices != null && Indices.Length > 0)
                {
                    // Intersect indexed geometry
                    for (faceIndex = 0; faceIndex < Indices.Length; faceIndex += 3)
                    {
                        Float3 vertex1 = Vertices[Indices[faceIndex]];
                        Float3 vertex2 = Vertices[Indices[faceIndex + 1]];
                        Float3 vertex3 = Vertices[Indices[faceIndex + 2]];

                        if (Ray.Intersects(modelSpaceRay, vertex1, vertex2, vertex3, out distance, out u, out v))
                        {
                            return true;
                        }
                    }
                }
                else
                {
                    // Intersect non-indexed geometry
                    for (faceIndex = 0; faceIndex < Vertices.Length; faceIndex += 3)
                    {
                        Float3 vertex1 = Vertices[faceIndex];
                        Float3 vertex2 = Vertices[faceIndex + 1];
                        Float3 vertex3 = Vertices[faceIndex + 2];

                        if (Ray.Intersects(modelSpaceRay, vertex1, vertex2, vertex3, out distance, out u, out v))
                        {
                            return true;
                        }
                    }
                }
            }

            faceIndex = -1;
            distance = u = v = -1f;
            return false;
        }

        /// <summary>
        /// Determines whether a ray intersects the geometry.
        /// </summary>
        /// <param name="transform">Transformation matrix of the geometry</param>
        /// <param name="ray">The ray which will be tested for intersection.</param>
        /// <param name="distance">When the method completes, contains the distance at which the ray intersected the plane.</param>
        /// <param name="faceIndex">When the method completes, contains the index of face which the ray intersects.</param>
        /// <param name="hits">All intersection hits.</param>
        /// <returns><c>true</c> if the ray intersects the plane; otherwise, <c>false</c>.</returns>
        public bool Intersects(Float4x4 transform, Ray ray, out float distance, out int faceIndex, out IntersectInformation[] hits)
        {
            var hitsList = new List<IntersectInformation>();
            
            float curDistance;
            int curIndex;
            
            distance = float.MaxValue;
            faceIndex = -1;

            // Create bounding sphere before inverting transform matrix
            BoundingSphere bs = CalculateBoundingSphere(transform);
            
            // Convert ray to model space
            Float3 near = ray.Position;
            Float3 dir = ray.Direction;
            transform.Invert();
            Float3 tmp = near;
            Float3.TransformCoordinate(ref tmp, ref transform, out near);
            tmp = dir;
            Float3.TransformNormal(ref tmp, ref transform, out dir);
            Ray modelSpaceRay = new Ray(near, dir);

            // Test bounding sphere first
            if (Ray.Intersects(ray, bs, out curDistance))
            {
                if (Indices != null && Indices.Length > 0)
                {
                    // Intersect indexed geometry
                    for (curIndex = 0; curIndex < Indices.Length; curIndex += 3)
                    {
                        Float3 vertex1 = Vertices[Indices[curIndex]];
                        Float3 vertex2 = Vertices[Indices[curIndex + 1]];
                        Float3 vertex3 = Vertices[Indices[curIndex + 2]];

                        float u, v;
                        if (Ray.Intersects(modelSpaceRay, vertex1, vertex2, vertex3, out curDistance, out u, out v))
                        {
                            if (curDistance < distance)
                            {
                                distance = curDistance;
                                faceIndex = curIndex / 3;
                            }

                            var hit = new IntersectInformation
                                          {
                                              Distance = curDistance,
                                              FaceIndex = faceIndex,
                                              U = u,
                                              V = v
                                          };
                            hitsList.Add(hit);
                        }
                    }
                }
                else
                {
                    // Intersect non-indexed geometry
                    for (curIndex = 0; curIndex < Vertices.Length; curIndex += 3)
                    {
                        Float3 vertex1 = Vertices[curIndex];
                        Float3 vertex2 = Vertices[curIndex + 1];
                        Float3 vertex3 = Vertices[curIndex + 2];

                        float u, v; 
                        if (Ray.Intersects(modelSpaceRay, vertex1, vertex2, vertex3, out curDistance, out u, out v))
                        {
                            if (curDistance < distance)
                            {
                                distance = curDistance;
                                faceIndex = curIndex / 3;
                            }

                            var hit = new IntersectInformation
                            {
                                Distance = curDistance,
                                FaceIndex = faceIndex,
                                U = u,
                                V = v
                            };
                            hitsList.Add(hit);
                        }
                    }
                }
            }

            hits = hitsList.ToArray();
            return hits.Length > 0;
        }
    }

Also ray you have to get from picked pixel on screen rather than from near/far. Have a look to this code:


void CalculatePickRay(int x, int y, int width, int height, float near, Float4x4 view, Float4x4 projection, out Float3 pickRayDir, out Float3 pickRayOrig)
        {
            pickRayDir.X = (((2.0f * x) / width) - 1);
            pickRayDir.Y = -(((2.0f * y) / height) - 1);
            pickRayDir.Z = 1.0f;

            projection.M41 = 0;
            projection.M42 = 0;
            projection.M43 = 0;
            projection.M44 = 1;
            projection.Invert();
            Float3 tmp = pickRayDir;
            Float3.TransformNormal(ref tmp, ref projection, out pickRayDir);

            // Get the inverse view matrix
            view.Invert();
            tmp = pickRayDir;
            Float3.TransformNormal(ref tmp, ref view, out pickRayDir);
            pickRayDir.Normalize();

            pickRayOrig.X = view.M41;
            pickRayOrig.Y = view.M42;
            pickRayOrig.Z = view.M43;

            // calc origin as intersection with near frustum
            pickRayOrig += pickRayDir * near;
        }
Advertisement

Interesting.. And if I'll make those simple primitives classes, would it help? If I'll keep my buffers for rendering and create such classes for each element representation? How do I then connect those classes with image on the screen ?

I need fast method, I have prety much primitives and iteration over them all is not a good solution for me..

My full goal is to get information about picked element - it's type and vertices. No other manipulations are needed (at least yet )

After reading this, I think you might be better off with a false color picking operation. Instead of trying to do this geometrically, you should instead render your elements with a special shader that produces an 'ID' color for its geometry. Then when you want to do picking, you simply read the render target contents back to system memory and look up which 'ID' your cursor was over.

This flattens your problem - instead of iterating over N objects on the CPU (which has maximum up to 8 cores), you are rasterizing the scene with your GPU (which uses hundreds or thousands of cores) and then just retrieving the results. I think in your situation, this would be a good solution.

void CalculatePickRay(int x, int y, int width, int height, float near, Float4x4 view, Float4x4 projection, out Float3 pickRayDir, out Float3 pickRayOrig)
{
pickRayDir.X = (((2.0f * x) / width) - 1);
pickRayDir.Y = -(((2.0f * y) / height) - 1);
pickRayDir.Z = 1.0f;

On Picking Ray construction, what does input params mean?
x and y - mouse position, as far as I understand
width and height - Viewport width and height
float near - ????

And at public bool Intersects(Float4x4 transform...

transform - it's a world matrix?

I'm not sure where you got these methods from, so I can't say definitively, but the typical inputs are as you described them. You need the x,y mouse coordinates, the size of the onscreen area that the view and perspective matrices are being applied to (i.e. the current viewport), then the view and perspective matrices. Basically you just need to construct a ray from the camera location (which is [0,0,0] in view space) to the point on the near clipping plane where the mouse cursor is.

The near clipping plane is probably what they are referring to with near. About the intersects method, I'm not really sure. The ray that you construct is typically supposed to be in world space, but it all depends on how you want to do the tests.

This topic is closed to new replies.

Advertisement