# ============================================================
# ROBOT WALK : RobotExpressive 操作デモ（3D）
#   ← → : 旋回   ↑ ↓ : 前進・後退（Z 押下中はダッシュ）   D : ダンス
#   Space : ジャンプ   X : パンチ   C : 手を振る
#   V : サムズアップ   B : うなずく   N : 首を振る
# ============================================================

ROBOT = Model.load("models/RobotExpressive.glb")

class Robot < ModelActor
  WALK  = 0.052
  RUN   = 0.13
  TURN  = 2.6
  RANGE = 60.0

  EMOTES = {
    Input::K_SPACE => :jump,
    Input::K_X     => :punch,
    Input::K_C     => :wave,
    Input::K_V     => :thumbs_up,
    Input::K_B     => :yes,
    Input::K_N     => :no,
  }.freeze

  def setup(**)
    @dance = false
    @direction = 1.0
    super(model: ROBOT, graph: build_graph)
  end

  def update
    @dance = !@dance if input.key_push?(Input::K_D)
    move
    EMOTES.each { |key, name| trigger(name) if input.key_push?(key) }
  end

  private

  # モーショングラフ。インスタンスメソッドで組むので、条件の Proc はこの個体を
  # 直接読める（条件は読み取り専用に保つこと）。ブレンド位置のようにグラフ機構が
  # 数値として使うものは params で渡す
  def build_graph
    {
      initial: :locomotion,
      params: { speed: 0.0, direction: 1.0 },
      states: {
        # speed 0〜2 で 静止→歩き→走り を連続ブレンド。歩幅は位相同期で揃え、
        # direction（±1）で前進・後退を丸ごと逆再生する
        locomotion: {
          blend: { 0.0 => "Idle", 1.0 => "Walking", 2.0 => "Running" },
          param: :speed, speed: :direction, sync: %w[Walking Running],
        },
        dance: { clip: "Dance" }, # lock なし = いつでも中断できる
        # once: 1 回再生して元の状態へ自動復帰。trigger: は :any からの遷移の略記。
        # lock: は退出ゲート（true = 完了まで、数値 = n 秒間キャンセル不可）
        jump:      { clip: "Jump",     once: true, trigger: :jump, lock: true },
        punch:     { clip: "Punch",    once: true, trigger: :punch, lock: 0.3 },
        wave:      { clip: "Wave",     once: true, trigger: :wave },
        thumbs_up: { clip: "ThumbsUp", once: true, trigger: :thumbs_up },
        yes:       { clip: "Yes",      once: true, trigger: :yes },
        no:        { clip: "No",       once: true, trigger: :no },
      },
      transitions: [
        # 条件はこの Robot のインスタンス変数を直接読む
        { from: :locomotion, to: :dance, if: -> { @dance },  fade: 0.5 },
        { from: :dance, to: :locomotion, if: -> { !@dance }, fade: 0.5 },
      ],
    }
  end

  def move
    turning = input.x != 0
    direction = -input.y # ↑ = +1（正面 +Z へ進む）
    @dance = false if turning || direction != 0

    self.rotation_y -= input.x * TURN

    dash = input.key_down?(Input::K_Z)
    if direction != 0
      step = (dash ? RUN : WALK) * direction
      radians = rotation_y * Math::PI / 180.0
      self.x = (x + Math.sin(radians) * step).clamp(-RANGE, RANGE)
      self.z = (z + Math.cos(radians) * step).clamp(-RANGE, RANGE)
    end

    # グラフへはパラメータを書くだけ。speed は減衰付きで歩き⇔走りが滑らかに混ざる。
    # direction は停止時も直前の向きを保持する（speed 減衰中に再生方向が反転しないように）
    @direction = direction.negative? ? -1.0 : 1.0 if direction != 0
    set(:speed, direction != 0 ? (dash ? 2.0 : 1.0) : (turning ? 1.0 : 0.0), damp: 0.12)
    set(:direction, @direction)
  end
end

class PlayScene < Scene
  CAMERA_DISTANCE = 9.0
  CAMERA_HEIGHT   = 4.5
  CAMERA_EASE     = 0.08

  def setup(**)
    @floor = spawn(Floor)
    @grid = spawn(Grid)
    @hemisphere_light = spawn(HemisphereLight)
    @sun = spawn(DirectionalLight)
    @robot = nil
    @camera_x = 0.0
    @camera_z = -CAMERA_DISTANCE
    window.bgcolor = 0xe0e0e0
    window.fog(0xe0e0e0, 20, 100)
    window.camera_at(@camera_x, CAMERA_HEIGHT, @camera_z)
    window.camera_look(0, 2, 0)
  end

  def update
    # モデルは非同期ロード。完了したフレームで登場させる
    @robot ||= spawn(Robot) if ROBOT.loaded?
    @robot&.update
    follow_camera if @robot
  end

  def draw
    Actor.draw(@floor, @grid, @hemisphere_light, @sun)
    @robot&.draw
  end

  private

  # ロボットの背後へ滑らかに回り込む追従カメラ
  def follow_camera
    radians = @robot.rotation_y * Math::PI / 180.0
    @camera_x += (@robot.x - Math.sin(radians) * CAMERA_DISTANCE - @camera_x) * CAMERA_EASE
    @camera_z += (@robot.z - Math.cos(radians) * CAMERA_DISTANCE - @camera_z) * CAMERA_EASE
    window.camera_at(@camera_x, CAMERA_HEIGHT, @camera_z)
    window.camera_look(@robot.x, @robot.y + 2.0, @robot.z)
  end
end

window = Window.new
world  = World.new(window)
window.world = world
world.push_scene(PlayScene)
window.run
