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