Пример #1
0
def begin_binding_form() -> None:
    controls: Ref[List[str]] = use_state('controls', [])
    listening_for: Ref[Optional[str]] = use_state('listening-for', None)
    waiting_for_release: Ref[bool] = use_state('waiting-for-release', False)

    if ig.is_mouse_clicked():
        listening_for.value = None

    if listening_for.value is not None:
        input = detect_input()

        if input is not None and not waiting_for_release.value:
            bindings[listening_for.value] = input
            config.settings['bindings'] = bindings_to_json(bindings)

            if listening_for.value in controls.value:
                index = controls.value.index(listening_for.value) + 1
                listening_for.value = controls.value[index] if index < len(
                    controls.value) else None
                waiting_for_release.value = True
            else:
                listening_for.value = None

        if input is None:
            waiting_for_release.value = False

    controls.value = []
Пример #2
0
  def do_render(id: str) -> None:
    nonlocal view

    if view is None:
      view = View(model)
    ig.push_id(id)

    log.timer.begin('render')
    view.render()
    log.timer.end()

    ig.pop_id()


    last_fps_time = use_state_with('last-fps-time', lambda: time.time())
    frame_count = use_state('frame-count', 0)
    fps = use_state('fps', 0.0)

    if hasattr(model, 'pipeline'):
      frame_count.value += 1
      if time.time() > last_fps_time.value + 5:
        fps.value = frame_count.value / (time.time() - last_fps_time.value)
        last_fps_time.value = time.time()
        frame_count.value = 0
        log.info(
          f'mspf: {int(1000 / fps.value * 10) / 10} ({int(fps.value)} fps)'
          f' - cache={model.pipeline.data_cache_size() // 1024}KB'
        )

      log.timer.begin('balance')
      model.pipeline.balance_distribution(1/120)
      log.timer.end()
Пример #3
0
def binding_button(name: str, label: str, width=0) -> None:
    listening_for: Ref[Optional[str]] = use_state('listening-for', None)

    controls: Ref[List[str]] = use_state('controls', [])
    controls.value.append(name)

    value = check_input(bindings.get(name))
    color = (0.26 + value * 0.7, 0.59 + value * 0.41, 0.98, 0.4)
    if listening_for.value == name:
        color = (0.86, 0.59, 0.98, 0.4)

    ig.push_style_color(ig.COLOR_BUTTON, *color)
    if ig.button(label, width=width):
        listening_for.value = name
    ig.pop_style_color()

    if ig.begin_popup_context_item('##btn-ctx-' + name):
        if ig.menu_item('Clear')[0] and name in bindings:
            del bindings[name]
        if ig.menu_item('Default')[0] and name in DEFAULT_BINDINGS:
            bindings[name] = DEFAULT_BINDINGS[name]
        ig.end_popup_context_item()

    ig.same_line()
    if name in bindings:
        text = str(bindings[name])
    else:
        text = 'Unbound'
    if listening_for.value == name:
        text = '(' + text + ')'

    if name in bindings:
        overlapping = find_overlapping_bindings().get(bindings[name])
    else:
        overlapping = None

    if overlapping is None:
        ig.text(text)
    else:
        ig.text_colored(text, 1.0, 0.8, 0.0, 1.0)
        if ig.is_item_hovered():

            def control_name(name: str) -> str:
                return name.replace('-', ' ').replace('n64', 'N64')

            overlapping = [c for c in overlapping if c != name]
            ig.set_tooltip('Also bound to ' +
                           ', '.join(map(control_name, overlapping)))
Пример #4
0
def input_pressed(name: str) -> bool:
    ig.push_id('ctrl-pressed-' + name)
    prev_down = use_state('prev-down', False)
    down = _input_down_uncond(name)
    pressed = not prev_down.value and down
    prev_down.value = down
    ig.pop_id()
    return pressed if ig.global_keyboard_capture() else False
Пример #5
0
    def render_right_column(self) -> None:
        total_height = ig.get_window_height()

        if self.show_debug_pane:
            ig.push_id('debug-pane')
            ig.begin_child('##pane', height=int(ig.get_window_height() * 0.15))
            ig.columns(2)
            ig.set_column_width(-1, ig.get_window_width() - 300)

            ig.begin_child('##log')

            def init_log() -> List[str]:
                messages = []
                log.subscribe(lambda msg: messages.append(str(msg)))
                return messages

            messages = use_state_with('messages', init_log).value
            prev_length = use_state('prev-length', 0)
            total_height -= ig.get_window_height()
            if prev_length.value != len(messages):
                prev_length.value = len(messages)
                ig.set_scroll_y(ig.get_scroll_max_y() + ig.get_window_height())
            for message in messages:
                ig.text(message)
            ig.end_child()

            ig.next_column()

            for line in log.timer.format(log.timer.get_summaries()):
                ig.text(line)

            ig.columns(1)
            ig.end_child()
            ig.pop_id()

        log.timer.begin('fsheet')
        frame_sheet = self.frame_sheets[0]
        ig.set_next_window_content_size(frame_sheet.get_content_width(), 0)
        ig.begin_child(
            'Frame Sheet##' + str(epoch) + '-0',
            height=int(total_height * 0.7),
            flags=ig.WINDOW_HORIZONTAL_SCROLLING_BAR,
        )
        frame_sheet.render()
        ig.end_child()

        if ig.begin_drag_drop_target():
            payload = ig.accept_drag_drop_payload('ve-var')
            if payload is not None:
                frame_sheet.append_variable(Variable.from_bytes(payload))
            ig.end_drag_drop_target()
        log.timer.end()

        log.timer.begin('varexp')
        ig.begin_child('Variable Explorer', border=True)
        self.variable_explorer.render('variable-explorer')
        ig.end_child()
        log.timer.end()
Пример #6
0
def input_pressed_repeat(name: str, delay: float, per_second: float) -> int:
    ig.push_id('ctrl-pressed-repeat-' + name)
    down_start: Ref[Optional[float]] = use_state('down-start', None)
    counted = use_state('counted', 0)

    down = _input_down_uncond(name)
    if not down:
        down_start.value = None
        count = 0
    elif down_start.value is None:
        down_start.value = time.time()
        counted.value = 0
        count = 1
    else:
        held = time.time() - down_start.value
        total_count = int(per_second * max(held - delay, 0))
        count = total_count - counted.value
        counted.value = total_count

    ig.pop_id()
    return count if ig.global_keyboard_capture() else 0
Пример #7
0
def input_down_gradual(name: str, until_full: float) -> float:
    ig.push_id('ctrl-down-gradual-' + name)
    down_start: Ref[Optional[float]] = use_state('down-start', None)

    down = _input_down_uncond(name)
    if not down:
        down_start.value = None
        result = 0.0
    elif down_start.value is None:
        down_start.value = time.time()
        result = 0.0
    else:
        result = min((time.time() - down_start.value) / until_full, 1)

    ig.pop_id()
    return result if ig.global_keyboard_capture() else 0.0
Пример #8
0
  def render(self) -> None:
    ig.push_id(str(epoch))

    # if ig.is_key_pressed(ord('`')):
    #   self.show_debug_pane = not self.show_debug_pane

    self.handle_controller()

    prev_frame_time = use_state_with('prev-frame-time', time.time)
    accum_time = use_state('accum-time', 0.0)
    now = time.time()
    accum_time.value += now - prev_frame_time.value
    prev_frame_time.value = now

    play_speed = self.model.play_speed
    if play_speed == 0.0:
      accum_time.value = 0
    else:
      target_fps = 30 * abs(play_speed)
      target_dt = 1 / target_fps
      updates = 0
      while accum_time.value >= target_dt and updates < 20:
        accum_time.value -= target_dt
        self.model.selected_frame += 1 if play_speed > 0 else -1
        self.handle_controller()
        updates += 1

    ig_window_size = ig.get_window_size()
    window_size = (int(ig_window_size.x), int(ig_window_size.y))

    ig.columns(2)
    self.render_left_column(window_size)
    ig.next_column()
    self.render_right_column()
    ig.columns(1)

    ig.pop_id()
Пример #9
0
  def handle_controller(self) -> None:
    ig.push_id('controller-inputs')

    buttons_enabled = use_state('buttons-enabled', False)
    stick_enabled = use_state('stick-enabled', False)

    def add_callbacks() -> Ref[bool]:
      input_edit = Ref(False)
      def disable_controller(*args, **kwargs) -> None:
        if not input_edit.value:
          buttons_enabled.value = False
          stick_enabled.value = False
      self.model.on_edit(disable_controller)

      def frame_change(*args, **kwargs) -> None:
        if self.model.play_speed == 0.0:
          disable_controller()
      self.model.on_selected_frame_change(frame_change)
      return input_edit
    input_edit = use_state_with('initialize', add_callbacks).value

    prev_play_speed = use_state('prev-play-speed', 0.0)
    if self.model.play_speed != prev_play_speed.value:
      buttons_enabled.value = False
      stick_enabled.value = False
    prev_play_speed.value = self.model.play_speed

    controller_button_values = {
      'input-button-a': input_down('n64-A'),
      'input-button-b': input_down('n64-B'),
      'input-button-z': input_down('n64-Z'),
      'input-button-s': input_down('n64-S'),
      'input-button-l': input_down('n64-L'),
      'input-button-r': input_down('n64-R'),
      'input-button-cu': input_down('n64-C^'),
      'input-button-cl': input_down('n64-C<'),
      'input-button-cr': input_down('n64-C>'),
      'input-button-cd': input_down('n64-Cv'),
      'input-button-du': input_down('n64-D^'),
      'input-button-dl': input_down('n64-D<'),
      'input-button-dr': input_down('n64-D>'),
      'input-button-dd': input_down('n64-Dv'),
    }
    if any(controller_button_values.values()):
      buttons_enabled.value = True
      stick_enabled.value = True
    for variable_name, new_button_value in controller_button_values.items():
      variable = Variable(variable_name).with_frame(self.model.selected_frame)
      button_value = self.model.get(variable)
      if buttons_enabled.value and button_value != new_button_value:
        input_edit.value = True
        self.model.set(variable, new_button_value)
        input_edit.value = False

    controller_stick_values = (
      input_float('n64->') - input_float('n64-<'),
      input_float('n64-^') - input_float('n64-v'),
    )
    # Require a larger magnitude for enabling controller since dead zone may be too small
    if any(abs(v) > 0.1 for v in controller_stick_values):
      stick_enabled.value = True
      buttons_enabled.value = True
    if stick_enabled.value:
      stick_x_var = Variable('input-stick-x').with_frame(self.model.selected_frame)
      stick_y_var = Variable('input-stick-y').with_frame(self.model.selected_frame)
      new_stick = self.compute_stick_from_controller(*controller_stick_values)
      stick = (self.model.get(stick_x_var), self.model.get(stick_y_var))
      if stick != new_stick:
        input_edit.value = True
        self.model.set(stick_x_var, new_stick[0])
        self.model.set(stick_y_var, new_stick[1])
        input_edit.value = False

    ig.pop_id()
Пример #10
0
  def render_left_column(self, framebuffer_size: Tuple[int, int]) -> None:
    total_height = ig.get_window_height() - ig.get_frame_height() # subtract menu bar
    slider_space = 45

    wall_hitbox_radius = use_state('wall-hitbox-radius', 50)
    wall_hitbox_options = [0, 24, 50, 110]

    hovered_surface: Ref[Optional[int]] = use_state('hovered-surface', None)
    new_hovered_surface: Optional[int] = None
    hidden_surfaces_by_area = \
      use_state('hidden-surfaces', cast(Dict[Tuple[int, int], Set[int]], {})).value

    current_area = (
      dcast(int, self.model.get(Variable('level-num').with_frame(self.model.selected_frame))),
      dcast(int, self.model.get(Variable('area-index').with_frame(self.model.selected_frame))),
    )
    hidden_surfaces = hidden_surfaces_by_area.setdefault(current_area, set())

    log.timer.begin('gview1')
    ig.begin_child(
      'Game View 1',
      height=int(total_height // 2) - slider_space // 2,
      border=True,
    )
    hovered_surface_1 = ui.render_game_view_rotate(
      'game-view-1',
      framebuffer_size,
      self.model,
      wall_hitbox_radius.value,
      hovered_surface.value,
      hidden_surfaces,
    )

    ig.set_cursor_pos((10.0, ig.get_window_height() - 30))
    ig.text('wall radius')
    ig.same_line()
    ig.push_item_width(50)
    _, index = ig.combo(
      '##wall-hitbox-radius',
      wall_hitbox_options.index(wall_hitbox_radius.value),
      list(map(str, wall_hitbox_options)),
    )
    wall_hitbox_radius.value = wall_hitbox_options[index]
    ig.pop_item_width()

    ig.end_child()
    log.timer.end()

    log.timer.begin('gview2')
    ig.begin_child(
      'Game View 2',
      height=int(total_height // 2) - slider_space // 2,
      border=True,
    )
    hovered_surface_2 = ui.render_game_view_birds_eye(
      'game-view-2',
      framebuffer_size,
      self.model,
      wall_hitbox_radius.value,
      hovered_surface.value,
      hidden_surfaces,
    )
    ig.end_child()
    log.timer.end()

    new_hovered_surface = hovered_surface_1 or hovered_surface_2
    if new_hovered_surface is not None and ig.is_mouse_clicked(1):
      ig.open_popup('surface-ctx')
      hovered_surface.value = new_hovered_surface

    if ig.begin_popup('surface-ctx'):
      if hovered_surface.value is not None:
        if hovered_surface.value in hidden_surfaces:
          if ig.menu_item('Show')[0]:
            hidden_surfaces.remove(hovered_surface.value)
        else:
          if ig.menu_item('Hide')[0]:
            hidden_surfaces.add(hovered_surface.value)
        if ig.menu_item('Properties')[0]:
          self.variable_explorer.open_surface_tab(hovered_surface.value)
      ig.end_popup()
    else:
      hovered_surface.value = new_hovered_surface

    if hovered_surface.value is not None and ig.is_mouse_clicked(2):
      if hovered_surface.value in hidden_surfaces:
        hidden_surfaces.remove(hovered_surface.value)
      else:
        hidden_surfaces.add(hovered_surface.value)


    speed_options = [0.05, 0.25, 0.5, 1, 2, 4]
    saved_play_direction = use_state('saved-play-direction', 0)
    saved_speed_index = use_state('saved-speed-index', 3)

    play_direction = saved_play_direction.value
    speed_index = saved_speed_index.value

    if play_direction == 0:
      frame_advance = 0
      play_override = 0

      def control(name: str, speed: int) -> None:
        nonlocal frame_advance, play_override
        x = input_down_gradual(name, 0.25)
        if x == 1.0:
          play_override = speed
        elif input_pressed(name):
          frame_advance += speed
      control('frame-next', 1)
      control('frame-next-alt', 1)
      control('frame-prev', -1)
      control('frame-prev-alt', -1)
      control('frame-next-fast', 10)
      control('frame-prev-fast', -10)

      if play_override != 0:
        if abs(play_override) in speed_options:
          speed_index = speed_options.index(abs(play_override))
        else:
          speed_index = len(speed_options) - 1
        play_direction = 1 if play_override > 0 else -1
      else:
        self.model.selected_frame += frame_advance

    else:
      if input_down('frame-next') or input_down('frame-next-alt'):
        if play_direction == 1:
          speed_index += 1
        else:
          play_direction = -play_direction
      elif input_down('frame-prev') or input_down('frame-prev-alt'):
        if play_direction == -1:
          speed_index += 1
        else:
          play_direction = -play_direction
      elif input_down('frame-next-fast'):
        if play_direction == 1:
          speed_index += 2
        else:
          play_direction = -play_direction
          speed_index += 1
      elif input_down('frame-prev-fast'):
        if play_direction == -1:
          speed_index += 2
        else:
          play_direction = -play_direction
          speed_index += 1
      speed_index = min(max(speed_index, 0), len(speed_options) - 1)

    self.model.play_speed = play_direction * speed_options[speed_index]
    self.model.playback_mode = saved_play_direction.value != 0

    def play_button(label: str, direction: int) -> None:
      disabled = play_direction == direction
      if ig.disableable_button(label, enabled=play_direction != direction):
        saved_play_direction.value = direction

    play_button('<|', -1)
    ig.same_line()
    play_button('||', 0)
    ig.same_line()
    play_button('|>', 1)
    ig.same_line()

    ig.push_item_width(63)
    changed, new_index = ig.combo(
      '##speed-option',
      speed_index,
      [str(s) + 'x' for s in speed_options],
    )
    ig.pop_item_width()
    if changed:
      saved_speed_index.value = new_index

    if input_pressed('playback-play'):
      if saved_play_direction.value == 0:
        saved_play_direction.value = 1
      else:
        saved_play_direction.value = 0
    if input_pressed('playback-rewind'):
      if saved_play_direction.value == 0:
        saved_play_direction.value = -1
      else:
        saved_play_direction.value = 0
    if input_pressed('playback-speed-up'):
      saved_speed_index.value = min(saved_speed_index.value + 1, len(speed_options) - 1)
    if input_pressed('playback-slow-down'):
      saved_speed_index.value = max(saved_speed_index.value - 1, 0)


    ig.same_line()
    new_frame = ui.render_frame_slider(
      'frame-slider',
      self.model.selected_frame,
      self.model.max_frame - 1,
      self.model.pipeline.cached_frames() if self.show_debug_pane else [],
    )
    if new_frame is not None:
      self.model.selected_frame = new_frame.value
Пример #11
0
def render_joystick_control(
    id: str,
    stick_x: float,
    stick_y: float,
    shape='square',
) -> Optional[Tuple[float, float]]:
    ig.push_id(id)
    state = use_state('', JoystickControlState()).value

    dl = ig.get_window_draw_list()

    padding = 10
    content_region = ig.get_content_region_available()
    size = min(
        content_region.x - ig.get_style().scrollbar_size - 2 * padding,
        content_region.y - 2 * padding,
        200,
    )
    size = max(size, 100)

    initial_cursor_pos = ig.get_cursor_pos()
    top_left = (
        initial_cursor_pos[0] + ig.get_window_position()[0] -
        ig.get_scroll_x() + padding,
        initial_cursor_pos[1] + ig.get_window_position()[1] -
        ig.get_scroll_y() + padding,
    )

    background_color = ig.get_color_u32_rgba(0, 0, 0, 0.3)
    if shape == 'square':
        dl.add_rect_filled(
            top_left[0],
            top_left[1],
            top_left[0] + size,
            top_left[1] + size,
            background_color,
        )
    elif shape == 'circle':
        dl.add_circle_filled(
            top_left[0] + size / 2,
            top_left[1] + size / 2,
            size / 2,
            background_color,
            num_segments=32,
        )

    result = None

    if state.active and ig.is_mouse_down():
        new_offset = state.get_value(ig.get_mouse_drag_delta(lock_threshold=0))

        new_stick_x = new_offset[0] / size * 2 - 1
        new_stick_y = (1 - new_offset[1] / size) * 2 - 1
        if shape == 'square':
            new_stick_x = min(max(new_stick_x, -1), 1)
            new_stick_y = min(max(new_stick_y, -1), 1)
        elif shape == 'circle':
            mag = math.sqrt(new_stick_x**2 + new_stick_y**2)
            if mag > 1:
                new_stick_x /= mag
                new_stick_y /= mag

        if (new_stick_x, new_stick_y) != (stick_x, stick_y):
            stick_x, stick_y = new_stick_x, new_stick_y
            result = (stick_x, stick_y)

    offset = (
        (stick_x + 1) / 2 * size,
        (1 - (stick_y + 1) / 2) * size,
    )

    dl.add_line(
        top_left[0] + size / 2,
        top_left[1] + size / 2,
        top_left[0] + offset[0],
        top_left[1] + offset[1],
        ig.get_color_u32_rgba(1, 1, 1, 0.5),
    )

    button_size = 20
    button_pos = (
        padding + initial_cursor_pos[0] + offset[0] - button_size / 2,
        padding + initial_cursor_pos[1] + offset[1] - button_size / 2,
    )
    ig.set_cursor_pos(button_pos)
    ig.button('##joystick-button', button_size, button_size)

    ig.set_cursor_pos((
        initial_cursor_pos[0],
        initial_cursor_pos[1] + size + 2 * padding,
    ))

    if ig.is_item_active():
        state.set_active(offset)
    else:
        state.reset()

    ig.pop_id()
    return result
Пример #12
0
def render_game_view_birds_eye(
    id: str,
    framebuffer_size: Tuple[int, int],
    model: Model,
    wall_hitbox_radius: float,
    hovered_surface: Optional[int],
    hidden_surfaces: Set[int],
) -> Optional[int]:
    ig.push_id(id)

    # TODO: Should zoom in on mouse when uncentered
    mouse_state = use_state('mouse-state', MouseTracker()).value
    zoom = use_state('zoom', -4.5)
    target: Ref[Optional[Tuple[float, float]]] = use_state('target', None)
    pos_y: Ref[Optional[float]] = use_state('pos-y', None)

    drag_amount = mouse_state.get_drag_amount()
    zoom.value += mouse_state.get_wheel_amount() / 5
    world_span_x = 200 / math.pow(2, zoom.value)

    viewport = get_viewport(framebuffer_size)

    mario_pos = get_mario_pos(model)

    # Camera xz

    camera_xz = (mario_pos[0], mario_pos[2])
    if target.value is not None:
        camera_xz = target.value

    if drag_amount != (0.0, 0.0):
        world_span_z = world_span_x * viewport.width / viewport.height
        if target.value is None:
            target.value = (mario_pos[0], mario_pos[2])
        target.value = (
            camera_xz[0] + drag_amount[1] * world_span_x / viewport.height,
            camera_xz[1] - drag_amount[0] * world_span_z / viewport.width,
        )
        camera_xz = target.value

    if ig.disableable_button('Lock to Mario', enabled=target.value
                             is not None):
        target.value = None

    # Camera y

    camera_y = mario_pos[1] + 500 if pos_y.value is None else pos_y.value

    ig.set_cursor_pos((viewport.width - 100, 10))
    ig.begin_child('##y-slider')
    new_y, reset = render_pos_y_slider('y-slider', camera_y, mario_pos[1])
    if reset:
        pos_y.value = None
    elif new_y is not None:
        pos_y.value = new_y
        camera_y = pos_y.value
    ig.end_child()

    camera = core.BirdsEyeCamera()
    camera.pos = (camera_xz[0], camera_y, camera_xz[1])
    camera.span_y = world_span_x

    # Mouse xz
    mouse_world_pos = get_mouse_world_pos_birds_eye(camera)
    mouse_ray: Optional[Tuple[Vec3f, Vec3f]]
    if mouse_world_pos is not None:
        ig.set_cursor_pos((10, viewport.height - 25))
        ig.text('(x, z) = (%.3f, %.3f)' % mouse_world_pos)
        mouse_ray = ((mouse_world_pos[0], camera.pos[1], mouse_world_pos[1]),
                     (0, -1, 0))
    else:
        mouse_ray = None

    if mouse_ray is None:
        new_hovered_surface = None
    else:
        new_hovered_surface = trace_ray(model, mouse_ray)

    render_game(
        model,
        viewport,
        camera,
        False,
        wall_hitbox_radius,
        hovered_surface=hovered_surface,
        hidden_surfaces=hidden_surfaces,
    )

    ig.pop_id()
    return new_hovered_surface
Пример #13
0
def use_rotational_camera(
    framebuffer_size: Tuple[int, int],
    model: Model,
) -> Tuple[core.RotateCamera, bool]:
    mouse_state = use_state('mouse-state', MouseTracker()).value
    target: Ref[Optional[Vec3f]] = use_state('target', None)
    target_vel: Ref[Optional[Vec3f]] = use_state('target-vel', None)
    pitch = use_state('pitch', 0.0)
    yaw = use_state('yaw', 0.0)
    zoom = use_state('zoom', 0.0)
    prev_frame_time = use_state_with('prev-frame-time', time.time)
    lock_to_in_game = use_state('lock-to-in-game', False)

    delta_time = time.time() - prev_frame_time.value
    prev_frame_time.value = time.time()

    drag_amount = mouse_state.get_drag_amount()
    pitch.value -= drag_amount[1] / 200
    yaw.value -= drag_amount[0] / 200
    wheel_amount = mouse_state.get_wheel_amount()
    zoom.value += wheel_amount / 5
    zoom.value = min(zoom.value, 7.0)

    mario_pos = get_mario_pos(model)
    target_pos = mario_pos if target.value is None else target.value

    fov_y = math.radians(45)

    if drag_amount != (0.0, 0.0) or wheel_amount != 0.0:
        lock_to_in_game.value = False

    if lock_to_in_game.value:
        target_pos = cast(
            Vec3f, model.get(model.selected_frame, 'gLakituState.focus'))
        target.value = target_pos
        camera_pos = cast(Vec3f,
                          model.get(model.selected_frame, 'gLakituState.pos'))
        dpos = (
            target_pos[0] - camera_pos[0],
            target_pos[1] - camera_pos[1],
            target_pos[2] - camera_pos[2],
        )
        pitch.value, yaw.value = direction_to_angle(dpos)
        offset = math.sqrt(sum(c**2 for c in dpos))
        if offset > 0.001:
            zoom.value = math.log(offset / 1500, 0.5)
        fov_y = math.radians(
            cast(float, model.get(model.selected_frame, 'sFOVState.fov')))

    offset = 1500 * math.pow(0.5, zoom.value)
    face_direction = angle_to_direction(pitch.value, yaw.value)

    move = [0.0, 0.0, 0.0]  # forward, up, right
    move[0] += input_float('3d-camera-move-f')
    move[0] -= input_float('3d-camera-move-b')
    move[1] += input_float('3d-camera-move-u')
    move[1] -= input_float('3d-camera-move-d')
    move[2] += input_float('3d-camera-move-r')
    move[2] -= input_float('3d-camera-move-l')

    if move != [0.0, 0.0, 0.0] or (target.value is not None
                                   and not lock_to_in_game.value):
        mag = math.sqrt(sum(c**2 for c in move))
        if mag > 1:
            move = [c / mag for c in move]

        max_speed = 50.0 * delta_time * math.sqrt(offset)
        f = (math.sin(yaw.value), 0, math.cos(yaw.value))
        u = (0, 1, 0)
        r = (-f[2], 0, f[0])
        end_vel = cast(
            Vec3f,
            tuple(max_speed * move[0] * f[i] + max_speed * move[1] * u[i] +
                  max_speed * move[2] * r[i] for i in range(3)))

        accel = 10.0 * delta_time * math.sqrt(offset)
        current_vel = target_vel.value or (0.0, 0.0, 0.0)
        target_vel.value = move_toward(current_vel, end_vel, accel)
        target.value = (
            target_pos[0] + target_vel.value[0],
            target_pos[1] + target_vel.value[1],
            target_pos[2] + target_vel.value[2],
        )
        target_pos = target.value
        lock_to_in_game.value = False

    if ig.disableable_button('Lock to Mario', enabled=target.value
                             is not None):
        target.value = None
        target_vel.value = None
        lock_to_in_game.value = False
    ig.same_line()
    if ig.disableable_button('Lakitu', enabled=not lock_to_in_game.value):
        lock_to_in_game.value = True

    camera_pos = (
        target_pos[0] - offset * face_direction[0],
        target_pos[1] - offset * face_direction[1],
        target_pos[2] - offset * face_direction[2],
    )

    camera = core.RotateCamera()
    camera.pos = camera_pos
    camera.target = target_pos
    camera.fov_y = fov_y

    show_camera_target = target.value is not None and not lock_to_in_game.value

    return camera, show_camera_target
Пример #14
0
    def render_frame_log_tab(self) -> None:
        frame_offset = use_state('frame-offset', 1)
        round_numbers = use_state('round-numbers', True)

        ig.push_item_width(210)
        _, frame_offset.value = ig.combo(
            '##frame-offset',
            frame_offset.value,
            ['previous -> current frame', 'current -> next frame'],
        )
        ig.pop_item_width()
        _, round_numbers.value = ig.checkbox('Round##round-numbers',
                                             round_numbers.value)
        ig.dummy(1, 10)

        events = self.model.pipeline.frame_log(self.model.selected_frame +
                                               frame_offset.value)

        def string(addr: object) -> str:
            return self.model.pipeline.read_string(0,
                                                   dcast(Address,
                                                         addr)).decode('utf-8')

        def f32(number: object) -> str:
            assert isinstance(number, float)
            if round_numbers.value:
                return '%.3f' % number
            else:
                return str(number)

        def vec3f(vector: object) -> str:
            return '(' + ', '.join(map(f32, dcast(list, vector))) + ')'

        def action(action: object) -> str:
            return self.model.action_names[dcast(int, action)]

        indent = 0
        action_indent = 0

        def show_text(text: str) -> None:
            ig.text('    ' * indent + text)

        for event in events:
            if event['type'] == 'FLT_CHANGE_ACTION':
                show_text(
                    f'change action: {action(event["from"])} -> {action(event["to"])}'
                )
            elif event['type'] == 'FLT_CHANGE_FORWARD_VEL':
                show_text(
                    f'change f vel: {f32(event["from"])} -> {f32(event["to"])} ({string(event["reason"])})'
                )
            elif event['type'] == 'FLT_WALL_PUSH':
                show_text(
                    f'wall push: {vec3f(event["from"])} -> {vec3f(event["to"])} (surface {event["surface"]})'
                )
            elif event['type'] == 'FLT_BEGIN_MOVEMENT_STEP':
                type_ = {1: 'air', 2: 'ground', 3: 'water'}[event['stepType']]
                show_text(f'{type_} step {event["stepNum"]}:')
                indent += 1
            elif event['type'] == 'FLT_END_MOVEMENT_STEP':
                indent -= 1
            elif event['type'] == 'FLT_EXECUTE_ACTION':
                indent -= action_indent
                action_indent = 0
                show_text(f'execute action: {action(event["action"])}')
                indent += 1
                action_indent += 1
            else:
                sorted_event = {'type': event['type']}
                sorted_event.update(sorted(event.items()))
                show_text(str(sorted_event))
Пример #15
0
    def render_intended_stick_control(self, id: str) -> None:
        up_options = ['3d view', 'mario yaw', 'stick y', 'world x']
        up_option = use_state('up-option', 0)

        ig.text('up =')
        ig.same_line()
        ig.push_item_width(100)
        _, up_option.value = ig.combo('##up-option', up_option.value,
                                      up_options)
        ig.pop_item_width()
        ig.dummy(1, 10)

        stick_x_var = Variable('input-stick-x').with_frame(
            self.model.selected_frame)
        stick_y_var = Variable('input-stick-y').with_frame(
            self.model.selected_frame)

        face_yaw = dcast(
            int,
            self.model.get(
                Variable('mario-face-yaw').with_frame(
                    self.model.selected_frame)))
        camera_yaw = dcast(
            int,
            self.model.get(
                Variable('camera-yaw').with_frame(self.model.selected_frame))
            or 0)
        squish_timer = dcast(
            int,
            self.model.get(self.model.selected_frame,
                           'gMarioState->squishTimer'))
        active_face_yaw = face_yaw

        events = self.model.pipeline.frame_log(self.model.selected_frame + 1)

        active_face_yaw_action = None
        for event in events:
            if event['type'] == 'FLT_EXECUTE_ACTION':
                action_name = self.model.action_names[event['action']]
                active_face_yaw = event['faceAngle'][1]
                active_face_yaw_action = action_name
                if action_name == 'idle':
                    break

        up_angle = {
            'mario yaw': active_face_yaw,
            'stick y': camera_yaw + 0x8000,
            'world x': 0x4000,
            '3d view': self.model.rotational_camera_yaw,
        }[up_options[up_option.value]]
        self.model.input_up_yaw = up_angle

        raw_stick_x = dcast(int, self.model.get(stick_x_var))
        raw_stick_y = dcast(int, self.model.get(stick_y_var))

        adjusted = stick_raw_to_adjusted(raw_stick_x, raw_stick_y)
        intended = stick_adjusted_to_intended(
            adjusted,
            face_yaw,
            camera_yaw,
            squish_timer != 0,
        )

        def render_value(label: str, value: object,
                         formatter: VariableFormatter) -> Optional[Any]:
            label_width = 60
            value_size = (
                60 if label == 'dyaw' else 80,
                ig.get_text_line_height() +
                2 * ig.get_style().frame_padding[1],
            )
            ig.push_item_width(label_width)
            ig.selectable(label, width=label_width)
            ig.pop_item_width()
            ig.same_line()
            new_value, _, _ = ui.render_variable_value('value-' + label, value,
                                                       formatter, value_size)
            return None if new_value is None else new_value.value

        target_yaw: Optional[int] = None
        target_dyaw: Optional[int] = None
        target_mag: Optional[float] = None

        target_mag = render_value('int mag', intended.mag, FloatFormatter())
        target_yaw = render_value('int yaw', intended.yaw,
                                  DecimalIntFormatter())
        dyaw = intended.yaw - active_face_yaw
        target_dyaw = render_value('dyaw', dyaw, DecimalIntFormatter())

        ig.same_line()
        if ig.button('?'):
            ig.open_popup('active-yaw-expl')
        if ig.begin_popup('active-yaw-expl'):
            ig.text(f'{intended.yaw} - {active_face_yaw} = {dyaw}')
            ig.text(f'intended yaw = {intended.yaw}')
            if active_face_yaw == face_yaw:
                ig.text(f'face yaw = {face_yaw}')
            if active_face_yaw != face_yaw:
                ig.text(
                    f'face yaw = {active_face_yaw} at start of {active_face_yaw_action} action'
                )
                ig.text(f'(face yaw = {face_yaw} at start of frame)')
            ig.end_popup()

        if dyaw not in range(0, 16):
            if ig.button('dyaw = 0'):
                target_dyaw = 0

        if target_yaw is not None or target_dyaw is not None or target_mag is not None:
            relative_to = 0 if target_yaw is not None else active_face_yaw
            if target_dyaw is not None:
                target_yaw = active_face_yaw + target_dyaw
            if target_yaw is None:
                target_yaw = intended.yaw
            if target_mag is None:
                target_mag = intended.mag

            new_raw_stick_x, new_raw_stick_y = intended_to_raw(
                face_yaw, camera_yaw, squish_timer, target_yaw, target_mag,
                relative_to)

            self.model.set(stick_x_var, new_raw_stick_x)
            self.model.set(stick_y_var, new_raw_stick_y)

        n_a = intended.yaw - up_angle
        n_x = intended.mag / 32 * math.sin(-n_a * math.pi / 0x8000)
        n_y = intended.mag / 32 * math.cos(n_a * math.pi / 0x8000)

        ig.set_cursor_pos((ig.get_cursor_pos().x + 155, 0))
        new_n = ui.render_joystick_control(id, n_x, n_y, 'circle')

        if new_n is not None:
            new_n_a = int(math.atan2(-new_n[0], new_n[1]) * 0x8000 / math.pi)
            new_intended_yaw = up_angle + new_n_a
            new_intended_mag = 32 * math.sqrt(new_n[0]**2 + new_n[1]**2)

            new_raw_stick_x, new_raw_stick_y = intended_to_raw(
                face_yaw,
                camera_yaw,
                squish_timer,
                new_intended_yaw,
                new_intended_mag,
                relative_to=0)

            self.model.set(stick_x_var, new_raw_stick_x)
            self.model.set(stick_y_var, new_raw_stick_y)
Пример #16
0
def _render_text(
    value: T,
    formatter: VariableFormatter,
    size: Tuple[int, int],
    highlight: bool,
) -> Tuple[Maybe[T], bool, bool]:
    editing = use_state('editing', False)
    initial_focus = use_state('initial-focus', False)

    if not editing.value:
        clicked, _ = ig.selectable(
            dcast(str, formatter.output(value)) + '##text',
            highlight,
            width=size[0],
            height=size[1],
            flags=ig.SELECTABLE_ALLOW_DOUBLE_CLICK,
        )

        if clicked:
            if ig.is_mouse_double_clicked():
                editing.value = True
                initial_focus.value = False

        pressed = ig.is_item_hovered() and ig.is_mouse_clicked()

        return None, clicked, pressed

    cursor_pos = ig.get_cursor_pos()
    cursor_pos = (
        ig.get_window_position()[0] + cursor_pos[0],
        ig.get_window_position()[1] + cursor_pos[1] - ig.get_scroll_y(),
    )

    ig.push_item_width(size[0])
    value_text = dcast(str, formatter.output(value))
    buffer_size = len(value_text) + ig.get_clipboard_length() + 1000
    _, input = ig.input_text('##text-edit', value_text, buffer_size)
    ig.pop_item_width()

    if not initial_focus.value:
        ig.set_keyboard_focus_here(-1)
        initial_focus.value = True
    elif not ig.is_item_active():
        editing.value = False

    try:
        input_value = formatter.input(input)
        assert type(input_value) is type(value)
        if input_value != value:
            return Just(cast(T, input_value)), False, False
    except:
        # TODO: Show error message
        dl = ig.get_window_draw_list()
        dl.add_rect(
            cursor_pos[0],
            cursor_pos[1],
            cursor_pos[0] + size[0],
            cursor_pos[1] + ig.get_text_line_height() +
            2 * ig.get_style().frame_padding[1],
            ig.get_color_u32_rgba(1, 0, 0, 1),
        )

    return None, False, False
Пример #17
0
def render_tabs(
    id: str,
    tabs: List[TabInfo],
    open_tab_index: Optional[int] = None,
    allow_windowing=False,
) -> Tuple[Optional[int], Optional[int]]:
    ig.push_id(id)
    root_id = get_local_state_id_stack()
    ig.columns(2)

    closed_tab = None

    rendered = use_state('rendered', False)
    if not rendered.value:
        rendered.value = True
        ig.set_column_width(-1, 120)

    if len(tabs) == 0:
        ig.pop_id()
        return None, closed_tab

    selected_tab_index = use_state_with('selected-tab-index',
                                        lambda: open_tab_index or 0)
    selected_tab_id = use_state_with('selected-tab',
                                     lambda: tabs[selected_tab_index.value].id)

    if open_tab_index is not None:
        selected_tab_index.value = open_tab_index
        selected_tab_id.value = tabs[open_tab_index].id

    windowed_tabs = use_state('windowed-tabs', cast(Set[str], set())).value

    # TODO: Change selected tab if windowed

    # Handle deletion/insertion
    if selected_tab_index.value >= len(tabs):
        selected_tab_index.value = len(tabs) - 1
    if tabs[selected_tab_index.value].id != selected_tab_id.value:
        matching_indices = [
            i for i in range(len(tabs)) if tabs[i].id == selected_tab_id.value
        ]
        if len(matching_indices) > 0:
            selected_tab_index.value = matching_indices[0]
        else:
            selected_tab_id.value = tabs[selected_tab_index.value].id

    ig.begin_child('tabs')
    for i, tab in enumerate(tabs):
        if tab.id in windowed_tabs:
            continue

        _, selected = ig.selectable(
            tab.label + '##tab-' + tab.id,
            selected_tab_id.value == tab.id,
        )
        if selected:
            selected_tab_index.value = i
            selected_tab_id.value = tab.id

        if tab.closable and ig.is_item_hovered() and ig.is_mouse_clicked(2):
            closed_tab = i

        if allow_windowing or tab.closable:
            if ig.begin_popup_context_item(f'##ctx-{tab.id}'):
                if allow_windowing and ig.selectable('Pop out')[0]:
                    windowed_tabs.add(tab.id)
                if tab.closable and ig.selectable('Close')[0]:
                    closed_tab = i
                ig.end_popup_context_item()

    ig.end_child()

    ig.next_column()

    ig.begin_child('content', flags=ig.WINDOW_HORIZONTAL_SCROLLING_BAR)
    tab = tabs[selected_tab_index.value]
    if tab.id not in windowed_tabs:
        push_local_state_rebase(('rebase-tabs', ) + root_id)
        tab.render(tab.id)  # type: ignore
        pop_local_state_rebase()
    ig.end_child()

    ig.columns(1)

    for tab_id in set(windowed_tabs):
        matching = [tab for tab in tabs if tab.id == tab_id]
        if len(matching) == 0:
            windowed_tabs.remove(tab.id)
            continue
        tab = matching[0]

        ig.set_next_window_size(*ig.get_window_size(), ig.ONCE)
        ig.set_next_window_position(*ig.get_window_position(), ig.ONCE)

        ig.push_style_color(ig.COLOR_WINDOW_BACKGROUND, 0.06, 0.06, 0.06, 0.94)
        _, opened = ig.begin(
            tab.label + '##window-' + tab.id,
            closable=True,
            flags=ig.WINDOW_HORIZONTAL_SCROLLING_BAR,
        )
        push_local_state_rebase(('rebase-tabs', ) + root_id)
        tab.render(tab.id)  # type: ignore
        pop_local_state_rebase()
        ig.end()
        ig.pop_style_color()

        if not opened:
            windowed_tabs.remove(tab.id)

    ig.pop_id()
    return (
        None if open_tab_index == selected_tab_index.value else
        selected_tab_index.value,
        closed_tab,
    )