using System.Collections.Generic;
using UnityEngine;
///
/// ムービングライトの制御
///
namespace StudioMaron
{
public class LightAngle : MonoBehaviour
{
public enum Axis
{
X,
Y,
Z
}
[Header("-----Tilt-------------------------------------------------------------------------------")]
public Transform tilt;
// Tiltで回転したいローカル軸
public Axis rotAxis_Tilt = Axis.X;
// Tiltのローカル回転の初期値
public Vector3 initRot_Tilt;
[Space(10)]
public bool debugTilt;
[Range(-90, 90)] public float debugTiltAngle;
[Header("-----Pan--------------------------------------------------------------------------------")]
public Transform pan;
// Panで回転したいローカル軸
public Axis rotAxis_Pan = Axis.Y;
// Panのローカル回転の初期値
public Vector3 initRot_Pan;
[Space(10)]
public bool debugPan;
[Range(-180, 180)] public float debugPanAngle;
[Header("-----Look At Target--------------------------------------------------------------------------------")]
// Look at target用のTiltの軸合わせ。[X,Y,Z,-X,-Y,-Z]の6パターンあるので正しい係数を1つ選ぶ
[Range(0, 5)] public int lookAtAxisAdjust_Tilt;
// Look at target用のPanの軸合わせ。[X,Y,Z]×[Y,Z,-Y,-Z]の12パターンあるので正しい係数を1つ選ぶ
[Range(0, 11)] public int lookAtAxisAdjust_Pan;
[Space(10)]
// 上記の軸合わせをする際につかう。ターゲットの方向を正しく向く係数を選ぶ。
public bool debugLookAt;
public Transform debugLookAtTarget;
// 計算用の変数
Quaternion init;
Quaternion end;
float noisePan;
float noiseTilt;
Vector3 noisePosition;
float weight;
bool invalid;
List infoList = new List();
Quaternion previousWorldRot_Pan = Quaternion.identity;
Quaternion previousWorldRot_Tilt = Quaternion.identity;
Quaternion previousLocalRot_Pan = Quaternion.identity;
Quaternion previousLocalRot_Tilt = Quaternion.identity;
// Inspectorの数値の変更があった場合、即時反映させる
private void OnValidate()
{
infoList.Clear();
var info = new LightAngleInfo();
// デバッグモードの場合、Inspectorの角度を反映させる
info.pan = debugPanAngle;
info.tilt = debugTiltAngle;
info.weight = 1;
if (debugLookAt && debugLookAtTarget)
{
info.isLookAtTarget = true;
info.targetPosition = debugLookAtTarget.position;
}
infoList.Add(info);
// デバッグモードでPanとTiltを適用する
SetPanTiltSlerp(infoList, true);
}
// デバッグモードの場合のみUpdateで計算する。通常はLight Angle Controllerからの制御待ち
private void Update()
{
if (debugTilt || debugPan || debugLookAt)
OnValidate();
}
// Light Angle Controllerから発動する。isDebugModeだと即時反映するが、通常はSlerp(previous, current, 0.1f)でローパスフィルタが入っている
public void SetPanTiltSlerp(List infoList, bool isDebugMode = false)
{
// infoListの中身が全て無効なら処理終了
invalid = true;
for (int i = 0; i < infoList.Count; i++)
{
invalid = invalid && infoList[i].isInvalid;
}
if (invalid) return;
weight = 0;
if (isDebugMode)
{
// デバッグモードの場合はTiltとPanを片方ずつ動かすので回転の初期化を行わない場合がある
if (debugPan)
pan.localRotation = Quaternion.identity;
if(debugTilt)
tilt.localRotation = Quaternion.identity;
if (debugLookAt)
{
pan.localRotation = Quaternion.identity;
tilt.localRotation = Quaternion.identity;
}
}
else
{
// 回転の初期化をする。この初期化はLook At Targetの時に正しく計算するのに必要
pan.localRotation = Quaternion.identity;
tilt.localRotation = Quaternion.identity;
}
for (int i = 0; i < infoList.Count; i++)
{
if (infoList[i].isInvalid) continue;
if (infoList[i].weight == 0) continue;
// Look At Targetの時の処理はグローバル回転で計算する必要がある。
if (infoList[i].isLookAtTarget)
{
if (pan == null) continue;
if (tilt == null) continue;
weight += infoList[i].weight;
noisePan = infoList[i].noiseRange * (-2 + 4 * perlinNoise(new Vector2(RandomFloat(infoList[i].noiseRandomSeed), Time.time * infoList[i].noiseSpeed * 3 - infoList[i].noiseRandomSeed)));
noiseTilt = infoList[i].noiseRange * (-2 + 4 * perlinNoise(new Vector2(RandomFloat(2 * infoList[i].noiseRandomSeed), Time.time * infoList[i].noiseSpeed * 3 + infoList[i].noiseRandomSeed)));
noisePosition = new Vector3(noisePan, 0, noiseTilt);
// Pan
init = Quaternion.Euler(initRot_Pan);
var pan_up = AxisAdjustPan(Dir.UP, lookAtAxisAdjust_Pan, pan);
var pan_forward = AxisAdjustPan(Dir.FORWARD, lookAtAxisAdjust_Pan, pan);
var panToTarget = infoList[i].targetPosition - pan.position - noisePosition;
var projection_p = panToTarget - Vector3.Dot(panToTarget, pan_up) * pan_up;
var angle_p = Vector3.Angle(pan_forward, projection_p);
if (angle_p < 179)
infoList[i].quaternion = Quaternion.FromToRotation(pan_forward, projection_p) * pan.rotation * init;
else
infoList[i].quaternion = Quaternion.AngleAxis(angle_p, pan_up) * pan.rotation * init; // since the object flips around 180 degrees.
end = Quaternion.Slerp(pan.rotation, infoList[i].quaternion, infoList[i].weight / weight);
pan.rotation = Quaternion.Slerp(previousWorldRot_Pan, end, isDebugMode ? 1 : 0.1f);
if (weight == 1)
{
previousLocalRot_Pan = pan.localRotation;
previousWorldRot_Pan = pan.rotation;
}
// Tilt
init = Quaternion.Euler(initRot_Tilt);
var tilt_up = AxisAdjustTilt(Dir.UP, lookAtAxisAdjust_Tilt, tilt); // TiltはUP方向が合っていればよい
var tiltToTarget = infoList[i].targetPosition - tilt.position - noisePosition;
var projection_t = tiltToTarget; // Panで既に向きの調節をしているので「シュミットの正規直行をしてRightベクトル成分の削除」の操作は不要
infoList[i].quaternion = Quaternion.FromToRotation(tilt_up, projection_t) * tilt.rotation * init;
end = Quaternion.Slerp(tilt.rotation, infoList[i].quaternion, infoList[i].weight / weight);
tilt.rotation = Quaternion.Slerp(previousWorldRot_Tilt, end, isDebugMode ? 1 : 0.1f);
if (weight == 1)
{
previousLocalRot_Tilt = tilt.localRotation;
previousWorldRot_Tilt = tilt.rotation;
}
}
// Look At Target以外の処理はローカル回転で行う
else
{
weight += infoList[i].weight;
if (pan != null)
{
init = Quaternion.Euler(initRot_Pan);
noisePan = infoList[i].noiseRange * (-180 + 360 * perlinNoise(new Vector2(RandomFloat(infoList[i].noiseRandomSeed), Time.time * infoList[i].noiseSpeed * 3 - infoList[i].noiseRandomSeed)));
infoList[i].quaternion = Q_Axis(rotAxis_Pan, infoList[i].pan + noisePan) * init;
end = Quaternion.Slerp(pan.localRotation, infoList[i].quaternion, infoList[i].weight / weight);
if (isDebugMode)
{
if(debugPan)
pan.localRotation = end;
}
else
{
pan.localRotation = Quaternion.Slerp(previousLocalRot_Pan, end, 0.1f);
}
if (weight == 1)
{
previousLocalRot_Pan = pan.localRotation;
previousWorldRot_Pan = pan.rotation;
}
}
if (tilt != null)
{
init = Quaternion.Euler(initRot_Tilt);
noiseTilt = infoList[i].noiseRange * (-45 + 90 * perlinNoise(new Vector2(RandomFloat(2 * infoList[i].noiseRandomSeed), Time.time * infoList[i].noiseSpeed * 3 + infoList[i].noiseRandomSeed)));
infoList[i].quaternion = Q_Axis(rotAxis_Tilt, infoList[i].tilt + noiseTilt) * init;
end = Quaternion.Slerp(tilt.localRotation, infoList[i].quaternion, infoList[i].weight / weight);
if (isDebugMode)
{
if(debugTilt)
tilt.localRotation = end;
}
else
{
tilt.localRotation = Quaternion.Slerp(previousLocalRot_Tilt, end, 0.1f);
}
if (weight == 1)
{
previousLocalRot_Tilt = tilt.localRotation;
previousWorldRot_Tilt = tilt.rotation;
}
}
}
}
}
// Inspectorで選んだ軸と、角度に合わせたQuaternionを返す
Quaternion Q_Axis(Axis axis, float degree)
{
if (degree == 0)
return Quaternion.identity;
switch (axis)
{
default:
return Quaternion.Euler(Vector3.right * degree);
case Axis.Y:
return Quaternion.Euler(Vector3.up * degree);
case Axis.Z:
return Quaternion.Euler(Vector3.forward * degree);
}
}
public enum Dir
{
UP,
FORWARD
}
// Look At Target用の軸合わせのTilt係数
Vector3 AxisAdjustTilt(Dir dir, int n, Transform t)
{
// Tilt: UPを6種類
switch (n)
{
default:
switch (dir) { default: return t.up;}
case 1:
switch (dir) { default: return -t.up;}
case 2:
switch (dir) { default: return t.right;}
case 3:
switch (dir) { default: return -t.right;}
case 4:
switch (dir) { default: return t.forward;}
case 5:
switch (dir) { default: return -t.forward;}
}
}
// Look At Target用の軸合わせのPan係数
Vector3 AxisAdjustPan(Dir dir, int n, Transform t)
{
// Pan: UPを3種類、FORWARDを4種類
switch (n)
{
default:
switch (dir) { default: return t.up; case Dir.FORWARD: return t.forward;}
case 1:
switch (dir) { default: return t.up; case Dir.FORWARD: return t.right;}
case 2:
switch (dir) { default: return t.up; case Dir.FORWARD: return -t.forward;}
case 3:
switch (dir) { default: return t.up; case Dir.FORWARD: return -t.right;}
case 4:
switch (dir) { default: return t.right; case Dir.FORWARD: return t.up;}
case 5:
switch (dir) { default: return t.right; case Dir.FORWARD: return t.forward;}
case 6:
switch (dir) { default: return t.right; case Dir.FORWARD: return -t.up;}
case 7:
switch (dir) { default: return t.right; case Dir.FORWARD: return -t.forward;}
case 8:
switch (dir) { default: return t.forward; case Dir.FORWARD: return t.right;}
case 9:
switch (dir) { default: return t.forward; case Dir.FORWARD: return t.up;}
case 10:
switch (dir) { default: return t.forward; case Dir.FORWARD: return -t.right;}
case 11:
switch (dir) { default: return t.forward; case Dir.FORWARD: return -t.up;}
}
}
///
/// 疑似乱数を作る
///
Vector2 st = new Vector2();
float RandomFloat(int seed)
{
st = new Vector2(seed, seed);
return frac(Mathf.Sin(Vector2.Dot(st, new Vector2(12.9898f, 78.233f))) * 43758.5453f);
}
float frac(float f)
{
return f - Mathf.Floor(f);
}
Vector2 random2(Vector2 vec)
{
vec = new Vector2(Vector2.Dot(vec, new Vector2(127.1f, 311.7f)), Vector2.Dot(vec, new Vector2(269.5f, 183.3f)));
var p = new Vector2(Mathf.Sin(vec.x), Mathf.Sin(vec.y)) * 43758.5453123f;
var frac = p - new Vector2(Mathf.Floor(p.x), Mathf.Floor(p.y));
return new Vector2(-1.0f, -1.0f) + 2.0f * frac;
}
float perlinNoise(Vector2 st)
{
Vector2 p = new Vector2(Mathf.Floor(st.x), Mathf.Floor(st.y));
Vector2 f = st - p;
Vector2 u = f * f * (new Vector2(3.0f, 3.0f) - 2.0f * f);
float v00 = random2(p + new Vector2(0, 0)).x;
float v10 = random2(p + new Vector2(1, 0)).x;
float v01 = random2(p + new Vector2(0, 1)).x;
float v11 = random2(p + new Vector2(1, 1)).x;
return Mathf.Lerp(Mathf.Lerp(Vector2.Dot(new Vector2(v00, v00), f - new Vector2(0, 0)), Vector2.Dot(new Vector2(v10, v10), f - new Vector2(1, 0)), u.x),
Mathf.Lerp(Vector2.Dot(new Vector2(v01, v01), f - new Vector2(0, 1)), Vector2.Dot(new Vector2(v11, v11), f - new Vector2(1, 1)), u.x), u.y) + 0.5f;
}
}
}