# cm diff cs:41 cs:42 (ServiceSim) # 1. cm diff の出力そのまま C "Assets/ServiceSim/Recording/SimTimeRecorder.cs" # 2. 中身の違い(補足。上の各ファイルを cm cat #cs:N で取り出し、diff -u で比べたもの) --- Assets/ServiceSim/Recording/SimTimeRecorder.cs#cs:41 +++ Assets/ServiceSim/Recording/SimTimeRecorder.cs#cs:42 @@ -24,8 +24,11 @@ /// ・自分で作った真上のカメラ(正投影・ふだんは切)を、撮るコマだけ /// RenderTexture に描いて読み、ffmpeg に生のまま渡して mp4 にする。 /// 画面の上の文字(OnGUI)は RenderTexture に入らない。 - /// ・店のもの・人・ロボット・光・ほかのカメラには触らない。 + /// ・店のもの・人・ロボット・ほかのカメラには触らない。 /// 見た目は Main Camera の設定(背景色・映すもの)を写して使う。 + /// ・光:影を落とす Directional Light(Setup の Sun)の高さだけを上げて影を短くする + /// (2026-10-29・依頼 28。地面からの角度 52° → 75°。方角・やわらかさ・濃さ・色・明るさは今のまま)。 + /// 撮り始めるときに変える。-ssRecord の無い回(夜の回・確かめの回・エディタの Play)では変わらない。 /// /// 引数 /// -ssRecord {名前の頭} … mp4 の名前の頭(例 20261029_1930_lpdemo_after) @@ -33,6 +36,11 @@ /// -ssRecFps 30 -ssRecCrf 16 /// -ssRecFfmpeg {ffmpeg のパス} /// -ssRecView name,x0,x1,y0,y1,w,h … 撮る範囲(店の座標 m)と大きさ。何本でも + /// -ssRecSunPitch 75 … 録画のときの光の地面からの角度(既定 75。依頼 28) + /// -ssRecStillAt 1500 -ssRecStillPitches 70,75,80 + /// … 静止画だけ書く回(依頼 28)。店の時刻がこの秒に来たコマを、 + /// 光の角度ごとに PNG で書いて、回を途中で終える(mp4 は作らない。 + /// 台帳には書かれず、記録は {run_stem}_stopped に残る) /// /// 置き場所:その回の出力フォルダ(HeadlessRun が決めた run_stem)の下の recordings/。 /// recordings/ がもうあれば撮らない(上書きしない)。 @@ -40,6 +48,8 @@ public class SimTimeRecorder : MonoBehaviour { const float CamHeight = 60f; + /// 録画のときの光の地面からの角度(依頼 28。ふだんの Sun は 52°) + const float DefaultSunPitch = 75f; class View { @@ -59,6 +69,10 @@ string _prefix = "", _ffmpeg = "ffmpeg"; float _from = 900f, _to = 1650f, _step = 0.2f; int _fps = 30, _crf = 16; + float _sunPitch = DefaultSunPitch; + float _stillAt = -1f; // 0 以上なら静止画だけ書く回 + readonly List _stillPitches = new List(); + Light _sun; readonly List _views = new List(); ExperimentRunner _runner; @@ -93,6 +107,16 @@ _step = F(Find(argv, "-ssRecStep"), _step); _fps = (int)F(Find(argv, "-ssRecFps"), _fps); _crf = (int)F(Find(argv, "-ssRecCrf"), _crf); + _sunPitch = F(Find(argv, "-ssRecSunPitch"), _sunPitch); + _stillAt = F(Find(argv, "-ssRecStillAt"), -1f); + string pitches = Find(argv, "-ssRecStillPitches"); + if (pitches != null) + foreach (string p in pitches.Split(',')) _stillPitches.Add(F(p, _sunPitch)); + if (_stillAt >= 0f) + { + if (_stillPitches.Count == 0) _stillPitches.Add(_sunPitch); + _from = _stillAt; _to = _stillAt; + } for (int i = 0; i < argv.Length - 1; i++) { @@ -123,6 +147,12 @@ _failed = true; return; } + if (_stillAt >= 0f) + { + Log("静止画だけ書きます: 店の時刻 " + _stillAt + " 秒・光の角度 " + string.Join(",", _stillPitches) + + "°・" + _views.Count + " 本。書いたら回を途中で終えます"); + return; + } Log("撮ります: 店の時刻 " + _from + "〜" + _to + " 秒・" + _step + " 秒ごと・" + _fps + " コマ/秒・CRF " + _crf + "・" + _views.Count + " 本"); } @@ -143,6 +173,8 @@ if (!_started && !Begin()) { _failed = true; return; } + if (_stillAt >= 0f) { WriteStills(t); return; } + for (int i = 0; i < _views.Count; i++) Grab(_views[i]); _csv.Write(_frames.ToString(CultureInfo.InvariantCulture)); _csv.Write(','); @@ -173,6 +205,10 @@ } Directory.CreateDirectory(_dir); + _sun = FindSun(); + if (_sun == null) Log("影を落とす Directional Light が1つに決まりません。光は変えずに撮ります"); + else SetSunPitch(_sunPitch); + Camera main = Camera.main; for (int i = 0; i < _views.Count; i++) { @@ -195,6 +231,7 @@ c.targetTexture = v.rt; v.cam = c; v.tex = new Texture2D(v.w, v.h, TextureFormat.RGB24, false); + if (_stillAt >= 0f) continue; // 静止画だけの回は ffmpeg を立てない v.mp4 = Path.Combine(_dir, _prefix + "_" + v.name + ".mp4"); string args = "-hide_banner -loglevel error -y -f rawvideo -pix_fmt rgb24 -s " + v.w + "x" + v.h + @@ -220,6 +257,62 @@ return true; } + /// 影を落とす Directional Light(Setup の Sun)。1つに決まらなければ null + static Light FindSun() + { + Light found = null; + foreach (Light l in FindObjectsOfType()) + { + if (l.type != LightType.Directional || l.shadows == LightShadows.None) continue; + if (found != null) return null; + found = l; + } + return found; + } + + /// 地面からの角度だけを変える(方角=y の回転・z の回転はそのまま) + void SetSunPitch(float pitch) + { + if (_sun == null) return; + Vector3 e = _sun.transform.eulerAngles; + float before = e.x; + _sun.transform.rotation = Quaternion.Euler(pitch, e.y, e.z); + Log("光の角度: " + _sun.name + " " + before.ToString("0.#", CultureInfo.InvariantCulture) + "° → " + + pitch.ToString("0.#", CultureInfo.InvariantCulture) + "°(方角 " + + e.y.ToString("0.#", CultureInfo.InvariantCulture) + "° のまま)"); + } + + /// 静止画だけの回:光の角度ごとに PNG を書いて、回を途中で終える + void WriteStills(float t) + { + _done = true; + StringBuilder note = new StringBuilder(); + for (int k = 0; k < _stillPitches.Count; k++) + { + float pitch = _stillPitches[k]; + SetSunPitch(pitch); + for (int i = 0; i < _views.Count; i++) + { + View v = _views[i]; + v.cam.Render(); + RenderTexture prev = RenderTexture.active; + RenderTexture.active = v.rt; + v.tex.ReadPixels(new Rect(0, 0, v.w, v.h), 0, 0, false); + v.tex.Apply(false); + RenderTexture.active = prev; + string png = Path.Combine(_dir, _prefix + "_" + v.name + "_t" + + t.ToString("0.##", CultureInfo.InvariantCulture) + "_sun" + + pitch.ToString("0.#", CultureInfo.InvariantCulture) + ".png"); + File.WriteAllBytes(png, v.tex.EncodeToPNG()); + note.Append("\n ").Append(png); + } + } + for (int i = 0; i < _views.Count; i++) if (_views[i].rt != null) _views[i].rt.Release(); + Log("静止画を書きました(店の時刻 " + t.ToString("0.###", CultureInfo.InvariantCulture) + " 秒)" + note + + "\n 回を途中で終えます(台帳には書きません)"); + Application.Quit(0); + } + void Grab(View v) { if (v.stdin == null) return;