using UnityEngine;
using System;
using Cinemachine.Utility;
namespace Cinemachine
{
/// Defines a group of target objects, each with a radius and a weight.
/// The weight is used when calculating the average position of the target group.
/// Higher-weighted members of the group will count more.
/// The bounding box is calculated by taking the member positions, weight,
/// and radii into account.
///
[DocumentationSorting(19, DocumentationSortingAttribute.Level.UserRef)]
[AddComponentMenu("Cinemachine/CinemachineTargetGroup")]
[SaveDuringPlay]
[ExecuteInEditMode]
public class CinemachineTargetGroup : MonoBehaviour
{
/// Holds the information that represents a member of the group
[DocumentationSorting(19.1f, DocumentationSortingAttribute.Level.UserRef)]
[Serializable] public struct Target
{
/// The target objects. This object's position and orientation will contribute to the
/// group's average position and orientation, in accordance with its weight
[Tooltip("The target objects. This object's position and orientation will contribute to the group's average position and orientation, in accordance with its weight")]
public Transform target;
/// How much weight to give the target when averaging. Cannot be negative
[Tooltip("How much weight to give the target when averaging. Cannot be negative")]
public float weight;
/// The radius of the target, used for calculating the bounding box. Cannot be negative
[Tooltip("The radius of the target, used for calculating the bounding box. Cannot be negative")]
public float radius;
}
/// How the group's position is calculated
[DocumentationSorting(19.2f, DocumentationSortingAttribute.Level.UserRef)]
public enum PositionMode
{
///Group position will be the center of the group's axis-aligned bounding box
GroupCenter,
/// Group position will be the weighted average of the positions of the members
GroupAverage
}
/// How the group's position is calculated
[Tooltip("How the group's position is calculated. Select GroupCenter for the center of the bounding box, and GroupAverage for a weighted average of the positions of the members.")]
public PositionMode m_PositionMode = PositionMode.GroupCenter;
/// How the group's orientation is calculated
[DocumentationSorting(19.3f, DocumentationSortingAttribute.Level.UserRef)]
public enum RotationMode
{
/// Manually set in the group's transform
Manual,
/// Weighted average of the orientation of its members.
GroupAverage
}
/// How the group's orientation is calculated
[Tooltip("How the group's rotation is calculated. Select Manual to use the value in the group's transform, and GroupAverage for a weighted average of the orientations of the members.")]
public RotationMode m_RotationMode = RotationMode.Manual;
/// This enum defines the options available for the update method.
public enum UpdateMethod
{
/// Updated in normal MonoBehaviour Update.
Update,
/// Updated in sync with the Physics module, in FixedUpdate
FixedUpdate,
/// Updated in MonoBehaviour LateUpdate.
LateUpdate
};
/// When to update the group's transform based on the position of the group members
[Tooltip("When to update the group's transform based on the position of the group members")]
public UpdateMethod m_UpdateMethod = UpdateMethod.LateUpdate;
/// The target objects, together with their weights and radii, that will
/// contribute to the group's average position, orientation, and size
[NoSaveDuringPlay]
[Tooltip("The target objects, together with their weights and radii, that will contribute to the group's average position, orientation, and size.")]
public Target[] m_Targets = new Target[0];
/// Cache of the last valid radius
private float m_lastRadius = 0;
/// The axis-aligned bounding box of the group, computed using the
/// targets positions and radii
public Bounds BoundingBox
{
get
{
float averageWeight;
Vector3 center = CalculateAveragePosition(out averageWeight);
bool gotOne = false;
Bounds b = new Bounds(center, new Vector3(m_lastRadius*2, m_lastRadius*2, m_lastRadius*2));
if (averageWeight > UnityVectorExtensions.Epsilon)
{
for (int i = 0; i < m_Targets.Length; ++i)
{
if (m_Targets[i].target != null)
{
float w = m_Targets[i].weight;
if (w < averageWeight - UnityVectorExtensions.Epsilon)
w = w / averageWeight;
else
w = 1;
float d = m_Targets[i].radius * 2 * w;
Vector3 p = Vector3.Lerp(center, m_Targets[i].target.position, w);
Bounds b2 = new Bounds(p, new Vector3(d, d, d));
if (!gotOne)
b = b2;
else
b.Encapsulate(b2);
gotOne = true;
}
}
}
Vector3 r = b.extents;
m_lastRadius = Mathf.Max(r.x, Mathf.Max(r.y, r.z));
return b;
}
}
/// Return true if there are no members with weight > 0
public bool IsEmpty
{
get
{
for (int i = 0; i < m_Targets.Length; ++i)
if (m_Targets[i].target != null && m_Targets[i].weight > UnityVectorExtensions.Epsilon)
return false;
return true;
}
}
/// The axis-aligned bounding box of the group, in a specific reference frame
/// The frame of reference in which to compute the bounding box
/// The axis-aligned bounding box of the group, in the desired frame of reference
public Bounds GetViewSpaceBoundingBox(Matrix4x4 mView)
{
Matrix4x4 inverseView = mView.inverse;
float averageWeight;
Vector3 center = inverseView.MultiplyPoint3x4(CalculateAveragePosition(out averageWeight));
bool gotOne = false;
Bounds b = new Bounds(center, new Vector3(m_lastRadius*2, m_lastRadius*2, m_lastRadius*2));
if (averageWeight > UnityVectorExtensions.Epsilon)
{
for (int i = 0; i < m_Targets.Length; ++i)
{
if (m_Targets[i].target != null)
{
float w = m_Targets[i].weight;
if (w < averageWeight - UnityVectorExtensions.Epsilon)
w = w / averageWeight;
else
w = 1;
float d = m_Targets[i].radius * 2;
Vector4 p = inverseView.MultiplyPoint3x4(m_Targets[i].target.position);
p = Vector3.Lerp(center, p, w);
Bounds b2 = new Bounds(p, new Vector3(d, d, d));
if (!gotOne)
b = b2;
else
b.Encapsulate(b2);
gotOne = true;
}
}
}
Vector3 r = b.extents;
m_lastRadius = Mathf.Max(r.x, Mathf.Max(r.y, r.z));
return b;
}
Vector3 CalculateAveragePosition(out float averageWeight)
{
Vector3 pos = Vector3.zero;
float weight = 0;
int numTargets = 0;
for (int i = 0; i < m_Targets.Length; ++i)
{
if (m_Targets[i].target != null && m_Targets[i].weight > UnityVectorExtensions.Epsilon)
{
++numTargets;
weight += m_Targets[i].weight;
pos += m_Targets[i].target.position * m_Targets[i].weight;
}
}
if (weight > UnityVectorExtensions.Epsilon)
pos /= weight;
if (numTargets == 0)
{
averageWeight = 0;
return transform.position;
}
averageWeight = weight / numTargets;
return pos;
}
Quaternion CalculateAverageOrientation()
{
Quaternion r = Quaternion.identity;
for (int i = 0; i < m_Targets.Length; ++i)
{
if (m_Targets[i].target != null)
{
float w = m_Targets[i].weight;
Quaternion q = m_Targets[i].target.rotation;
// This is probably bogus
r = new Quaternion(r.x + q.x * w, r.y + q.y * w, r.z + q.z * w, r.w + q.w * w);
}
}
return r.Normalized();
}
private void OnValidate()
{
for (int i = 0; i < m_Targets.Length; ++i)
{
if (m_Targets[i].weight < 0)
m_Targets[i].weight = 0;
if (m_Targets[i].radius < 0)
m_Targets[i].radius = 0;
}
}
void FixedUpdate()
{
if (m_UpdateMethod == UpdateMethod.FixedUpdate)
UpdateTransform();
}
void Update()
{
if (!Application.isPlaying || m_UpdateMethod == UpdateMethod.Update)
UpdateTransform();
}
void LateUpdate()
{
if (m_UpdateMethod == UpdateMethod.LateUpdate)
UpdateTransform();
}
void UpdateTransform()
{
if (IsEmpty)
return;
switch (m_PositionMode)
{
case PositionMode.GroupCenter:
transform.position = BoundingBox.center;
break;
case PositionMode.GroupAverage:
float averageWeight;
transform.position = CalculateAveragePosition(out averageWeight);
break;
}
switch (m_RotationMode)
{
case RotationMode.Manual:
break;
case RotationMode.GroupAverage:
transform.rotation = CalculateAverageOrientation();
break;
}
}
}
}