2026-02-27 22:54:56 +09:00

338 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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