338 lines
15 KiB
C#
338 lines
15 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
/// <summary>
|
||
/// ムービングライトの制御
|
||
/// </summary>
|
||
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<LightAngleInfo> infoList = new List<LightAngleInfo>();
|
||
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<LightAngleInfo> 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;}
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 疑似乱数を作る
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
}
|
||
}
|