Raycasting

Updated: 01/30/2020

Summary

Raycasting is a function that determines whether a user-specified Cubism mesh intersects with the set coordinates.
It is possible to obtain the clicked/tapped coordinates or a collision detection between any object in the scene and a specified mesh.
Click here for more information on how to use Raycasting.

Raycasting in the Cubism SDK for Unity consists of two major components.

  1. Components for specifying the ArtMesh to be judged
  2. Components that perform collision detection between input coordinates and the specified ArtMeshes

1. Components for Specifying the ArtMesh to Be Judged

Use CubismRaycastable to specify the mesh to be used for collision detection.

CubismRaycastable is used by attaching it to GameObjects placed under [Prefab root]/Drawables/.
The ArtMesh with the same ID as the GameObject to which it is attached is used for collision detection.

CubismRaycastable has a parameter that sets the accuracy of its mesh collision detection.

namespace Live2D.Cubism.Framework.Raycasting
{
    /// <summary>
    /// Allows raycasting against <see cref="Core.CubismDrawable"/>s.
    /// </summary>
    public sealed class CubismRaycastable : MonoBehaviour
    {
        /// <summary>
        /// The precision.
        /// </summary>
        public CubismRaycastablePrecision Precision;
    }
}

CubismRaycastable.Precision has two values that can be set, each as follows.

  • BoundingBox

A rectangle with four horizontal or vertical sides that encloses the mesh is used for the collision detection.
Depending on the mesh shape, it may also take collisions at coordinates outside the mesh, but it offers better performance than Triangles.

  • Triangles

The shape of the mesh is used as the range of the collision detection.
It is less performant than the BoundingBox, but it can make judgments within an accurate range.

namespace Live2D.Cubism.Framework.Raycasting
{
    /// <summary>
    /// Precision for casting rays against a <see cref="CubismRaycastable"/>.
    /// </summary>
    public enum CubismRaycastablePrecision
    {
        /// <summary>
        /// Cast against bounding box.
        /// </summary>
        BoundingBox,

        /// <summary>
        /// Cast against triangles.
        /// </summary>
        Triangles
    }
}

2. Components that Perform Collision Detection between Input Coordinates and the Specified ArtMeshes

CubismRaycaster is used to obtain the actual collision detection.
When using CubismRaycaster, it is attached to the root of Cubism’s Prefab.

Gets a reference to all CubismRaycastables attached to the Prefab during initialization of the CubismRaycaster.
When a collision mesh is added/removed during execution, CubismRaycaster.Refresh() is called to reacquire the reference.

        /// <summary>
        /// Refreshes the controller. Call this method after adding and/or removing <see cref="CubismRaycastable"/>.
        /// </summary>
        private void Refresh()
        {
            var candidates = this
                .FindCubismModel()
                .Drawables;


            // Find raycastable drawables.
            var raycastables = new List<CubismRenderer>();
            var raycastablePrecisions = new List<CubismRaycastablePrecision>();


            for (var i = 0; i < candidates.Length; i++)
            {
                // Skip non-raycastables.
                if (candidates[i].GetComponent<CubismRaycastable>() == null)
                {
                    continue;
                }


                raycastables.Add(candidates[i].GetComponent<CubismRenderer>());
                raycastablePrecisions.Add(candidates[i].GetComponent<CubismRaycastable>().Precision);
            }


            // Cache raycastables.
            Raycastables = raycastables.ToArray();
            RaycastablePrecisions = raycastablePrecisions.ToArray();
        }

        ...

        /// <summary>
        /// Called by Unity. Makes sure cache is initialized.
        /// </summary>
        private void Start()
        {
            // Initialize cache.
            Refresh();
        }

To get a collision from the coordinates, use CubismRaycaster.Raycast().

        /// <summary>
        /// Casts a ray.
        /// </summary>
        /// <param name="origin">The origin of the ray.</param>
        /// <param name="direction">The direction of the ray.</param>
        /// <param name="result">The result of the cast.</param>
        /// <param name="maximumDistance">[Optional] The maximum distance of the ray.</param>
        /// <returns><see langword="true"/> in case of a hit; <see langword="false"/> otherwise.</returns>
        /// <returns>The numbers of drawables had hit</returns>
        public int Raycast(Vector3 origin, Vector3 direction, CubismRaycastHit[] result, float maximumDistance = Mathf.Infinity)
        {
            return Raycast(new Ray(origin, direction), result, maximumDistance);
        }

        /// <summary>
        /// Casts a ray.
        /// </summary>
        /// <param name="ray"></param>
        /// <param name="result">The result of the cast.</param>
        /// <param name="maximumDistance">[Optional] The maximum distance of the ray.</param>
        /// <returns><see langword="true"/> in case of a hit; <see langword="false"/> otherwise.</returns>
        /// <returns>The numbers of drawables had hit</returns>
        public int Raycast(Ray ray, CubismRaycastHit[] result, float maximumDistance = Mathf.Infinity)
        {
            // Cast ray against model plane.
            var intersectionInWorldSpace = ray.origin + ray.direction * (ray.direction.z / ray.origin.z);
            var intersectionInLocalSpace = transform.InverseTransformPoint(intersectionInWorldSpace);


            intersectionInLocalSpace.z = 0;


            var distance = intersectionInWorldSpace.magnitude;


            // Return non-hits.
            if (distance > maximumDistance)
            {
                return 0;
            }

            // Cast against each raycastable.
            var hitCount = 0;


            for (var i = 0; i < Raycastables.Length; i++)
            {
                var raycastable = Raycastables[i];
                var raycastablePrecision = RaycastablePrecisions[i];


                // Skip inactive raycastables.
                if (!raycastable.MeshRenderer.enabled)
                {
                    continue;
                }



                if (raycastablePrecision == CubismRaycastablePrecision.BoundingBox)
                {
                    var bounds = raycastable.Mesh.bounds;

                    // Skip non hits
                    if (!bounds.Contains(intersectionInLocalSpace))
                    {
                        continue;
                    }
                }
                else
                {
                    // Skip non hits
                    if (!ContainsInTriangles(raycastable.Mesh, intersectionInLocalSpace))
                    {
                        continue;
                    }
                }


                result[hitCount].Drawable = raycastable.GetComponent<CubismDrawable>();
                result[hitCount].Distance = distance;
                result[hitCount].LocalPosition = intersectionInLocalSpace;
                result[hitCount].WorldPosition = intersectionInWorldSpace;


                ++hitCount;


                // Exit if result buffer is full.
                if (hitCount == result.Length)
                {
                    break;
                }
            }


            return hitCount;
        }

CubismRaycaster.Raycast() returns the number of meshes that get collision detection in the return value.
In addition, information on the mesh from which the collision detection was obtained is set to the instance of the CubismRaycastHit[] type passed as an argument.

            var raycastResults = new CubismRaycastHit[4]; var hitCount = _raycaster.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), raycastResults);

CubismRaycaster.Raycast() retrieves up to the number of elements of an instance of CubismRaycastHit[] type if multiple meshes overlap at the same coordinates.
If there are more overlapping meshes than the number of elements, the results will not be retrieved for the overlapping meshes.

CubismRaycastHit is a structure with information about the mesh from which the collision was obtained.

using Live2D.Cubism.Core;
using UnityEngine;


namespace Live2D.Cubism.Framework.Raycasting
{
    /// <summary>
    /// Contains raycast information.
    /// </summary>
    public struct CubismRaycastHit
    {
        /// <summary>
        /// The hit <see cref="CubismDrawable"/>.
        /// </summary>
        public CubismDrawable Drawable;

        /// <summary>
        /// The distance the ray traveled until it hit the <see cref="CubismDrawable"/>.
        /// </summary>
        public float Distance;

        /// <summary>
        /// The hit position local to the <see cref="CubismDrawable"/>.
        /// </summary>
        public Vector3 LocalPosition;

        /// <summary>
        /// The hit position in world coordinates.
        /// </summary>
        public Vector3 WorldPosition;
    }
}
  • Drawable

This is a reference to the ArtMesh from which the collision detection was obtained.

  • Distance

The distance from the specified coordinates.
The linear distance between origin or ray.origin passed as an argument to CubismRaycaster.Raycast() and Transform.position of the Drawable.

  • LocalPosition

These are the local coordinates of the ArtMesh from which the collision detection was obtained.

  • WorldPosition

These are the world coordinates of the ArtMesh from which the collision detection was obtained.

Please let us know what you think about this article.