/**
* Copyright(c) Live2D Inc. All rights reserved.
*
* Use of this source code is governed by the Live2D Open Software license
* that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
*/
using Live2D.Cubism.Core;
using Live2D.Cubism.Rendering;
using System.Collections.Generic;
using UnityEngine;
namespace Live2D.Cubism.Framework.Raycasting
{
///
/// Allows casting rays against s.
///
public sealed class CubismRaycaster : MonoBehaviour
{
///
/// s with s attached.
///
private CubismRenderer[] Raycastables { get; set; }
///
/// s with s attached.??????????????
///
private CubismRaycastablePrecision[] RaycastablePrecisions { get; set; }
///
/// Refreshes the controller. Call this method after adding and/or removing .
///
private void Refresh()
{
var candidates = this
.FindCubismModel()
.Drawables;
// Find raycastable drawables.
var raycastables = new List();
var raycastablePrecisions = new List();
for (var i = 0; i < candidates.Length; i++)
{
// Skip non-raycastables.
if (candidates[i].GetComponent() == null)
{
continue;
}
raycastables.Add(candidates[i].GetComponent());
raycastablePrecisions.Add(candidates[i].GetComponent().Precision);
}
// Cache raycastables.
Raycastables = raycastables.ToArray();
RaycastablePrecisions = raycastablePrecisions.ToArray();
}
#region Unity Event Handling
///
/// Called by Unity. Makes sure cache is initialized.
///
private void Start()
{
// Initialize cache.
Refresh();
}
#endregion
///
/// Casts a ray.
///
/// The origin of the ray.
/// The direction of the ray.
/// The result of the cast.
/// [Optional] The maximum distance of the ray.
/// in case of a hit; otherwise.
/// The numbers of drawables had hit
public int Raycast(Vector3 origin, Vector3 direction, CubismRaycastHit[] result, float maximumDistance = Mathf.Infinity)
{
return Raycast(new Ray(origin, direction), result, maximumDistance);
}
///
/// Casts a ray.
///
///
/// The result of the cast.
/// [Optional] The maximum distance of the ray.
/// in case of a hit; otherwise.
/// The numbers of drawables had hit
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;
}
var bounds = raycastable.Mesh.bounds;
// Skip non hits (bounding box)
if (!bounds.Contains(intersectionInLocalSpace))
{
continue;
}
// Do detailed hit-detection against mesh if requested.
if (raycastablePrecision == CubismRaycastablePrecision.Triangles)
{
if (!ContainsInTriangles(raycastable.Mesh, intersectionInLocalSpace))
{
continue;
}
}
result[hitCount].Drawable = raycastable.GetComponent();
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;
}
///
/// Check the point is inside polygons.
///
///
///
///
private bool ContainsInTriangles(Mesh mesh, Vector3 inputPosition)
{
for (var i = 0; i < mesh.triangles.Length; i+=3)
{
var vertexPositionA = mesh.vertices[mesh.triangles[i]];
var vertexPositionB = mesh.vertices[mesh.triangles[i + 1]];
var vertexPositionC = mesh.vertices[mesh.triangles[i + 2]];
var crossProduct1 =
(vertexPositionB.x - vertexPositionA.x) * (inputPosition.y - vertexPositionB.y) -
(vertexPositionB.y - vertexPositionA.y) * (inputPosition.x - vertexPositionB.x);
var crossProduct2 =
(vertexPositionC.x - vertexPositionB.x) * (inputPosition.y - vertexPositionC.y) -
(vertexPositionC.y - vertexPositionB.y) * (inputPosition.x - vertexPositionC.x);
var crossProduct3 =
(vertexPositionA.x - vertexPositionC.x) * (inputPosition.y - vertexPositionA.y) -
(vertexPositionA.y - vertexPositionC.y) * (inputPosition.x - vertexPositionA.x);
if ((crossProduct1 > 0 && crossProduct2 > 0 && crossProduct3 > 0) ||
(crossProduct1 < 0 && crossProduct2 < 0 && crossProduct3 < 0))
{
return true;
}
}
return false;
}
}
}