/** * 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; } } }