Интерполяция при переключении режимов камеры должна работать в обе стороны.
Код:
# --- Start of main.py ---
import pygame
import math
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
from plane_module import Plane
from game_objects import FBXModel
from interface_module import GameInterface

# Инициализация Pygame и настройка окна
pygame.init()
screen = pygame.display.set_mode((800, 600), DOUBLEBUF | OPENGL)
pygame.display.set_caption("3D Shooter with OpenGL")

# Включение необходимых функций OpenGL
glEnable(GL_DEPTH_TEST)
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glEnable(GL_COLOR_MATERIAL)
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)

# Настройка освещения
glLightfv(GL_LIGHT0, GL_POSITION, (0, 100, 0, 1))
glLightfv(GL_LIGHT0, GL_AMBIENT, (0.2, 0.2, 0.2, 1))
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.8, 0.8, 0.8, 1))

# Переменные игрока
player_x, player_y, player_z = 0, 0, 0
move_speed = 70.0  # Скорость движения в обычном режиме
jump_force = 2.5
gravity = 5.0

# Параметры камеры для режимов объединены в словарь
camera_modes = {
    'third_person': { 
        'distance': 20,
        'height': 5,
    },
    'first_person': {
        'distance': 10,
        'height': 2,
        'offset_forward': 0,  # Смещение вперёд (временно ус тановлено на 0)
        'offset_up': 0        # Смещение вверх (временно установлено на 0)
    }
}

# Инициализация начальных значений камеры (режим от первого лица)
camera_distance = camera_modes['first_person']['distance']
camera_height = camera_modes['first_person']['height']
camera_pitch = 0
camera_yaw = 0
mouse_sensitivity = 0.2
min_pitch, max_pitch = -89, 89

# Флаг для режима камеры от третьего лица
third_person_mode = False

# Инициализация объектов игры
plane = Plane(size=20000, grid=50, heightmap="Height_Map.exr", diffusemap="Diffuse_Map.exr", height_scale=20000)
model = FBXModel(model_path="characterMedium.obj", position=[player_x, player_y, player_z], scale=7)
interface = GameInterface(minimap_filename="mini_map.png", minimap_size=200)

# Флаг для режима noclip
noclip_mode = False

# Добавим новые переменные для хранения начальных и целевых значений параметров камеры
initial_camera_params = {
    'position': model.position.copy(),
    'yaw': camera_yaw,
    'pitch': camera_pitch,
    'distance': camera_distance,
    'height': camera_height
}

target_camera_params = initial_camera_params.copy()

# Переменная для отслеживания прогресса интерполяции
interpolation_progress = 0.0
interpolation_duration = 1.2  # Длительность интерполяции в секундах

def set_camera(player_x, player_y, player_z, yaw, pitch, distance, height, interpolation_factor=1.0):
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(60, (800 / 600), 0.1, 10000.0)

    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

    if third_person_mode:
        # Режим камеры от третьего лица
        look_x = player_x + math.sin(math.radians(yaw)) * math.cos(math.radians(pitch))
        look_y = player_y + math.sin(math.radians(pitch))
        look_z = player_z - math.cos(math.radians(yaw)) * math.cos(math.radians(pitch))

        # Позиция камеры от третьего лица
        camera_offset_x = player_x - math.sin(math.radians(yaw)) * distance
        camera_offset_z = player_z + math.cos(math.radians(yaw)) * distance
        camera_offset_y = player_y + height

        gluLookAt(
            camera_offset_x, camera_offset_y, camera_offset_z,
            look_x, look_y, look_z,  # Цель камеры смотрит на игрока
            0, 1, 0  # Вектор "вверх" остаётся неизменным
        )
    else:
        # Режим камеры от первого лица
        offset_forward = camera_modes['first_person']['offset_forward']
        offset_up = camera_modes['first_person']['offset_up']

        camera_offset_x = player_x + math.sin(math.radians(yaw)) * distance + math.sin(math.radians(yaw)) * offset_forward
        camera_offset_z = player_z - math.cos(math.radians(yaw)) * distance + math.cos(math.radians(yaw)) * offset_forward
        camera_offset_y = player_y + height + offset_up

        look_x = camera_offset_x + math.sin(math.radians(yaw)) * math.cos(math.radians(pitch))
        look_y = camera_offset_y + math.sin(math.radians(pitch))
        look_z = camera_offset_z - math.cos(math.radians(yaw)) * math.cos(math.radians(pitch))

        gluLookAt(
            camera_offset_x, camera_offset_y, camera_offset_z,
            look_x, look_y, look_z,
            0, 1, 0
        )

# Инициализация камеры при запуске игры
set_camera(*model.position, camera_yaw, camera_pitch, camera_distance, camera_height)

clock = pygame.time.Clock()
pygame.mouse.set_visible(False)
pygame.event.set_grab(True)
vertical_momentum = 0
is_grounded = True
running = True

while running:
    dt = clock.tick(60) / 1000  # Исправленный расчёт времени

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glClearColor(0.53, 0.81, 0.98, 1)

    # Обработка интерполяции
    if interpolation_progress < interpolation_duration:
        interpolation_progress += dt
        factor = interpolation_progress / interpolation_duration

        # Интерполяция параметров камеры
        interpolated_camera_params = {
            'position': [
                initial_camera_params['position'][0] * (1 - factor) + target_camera_params['position'][0] * factor,
                initial_camera_params['position'][1] * (1 - factor) + target_camera_params['position'][1] * factor,
                initial_camera_params['position'][2] * (1 - factor) + target_camera_params['position'][2] * factor
            ],
            'yaw': initial_camera_params['yaw'] * (1 - factor) + target_camera_params['yaw'] * factor,
            'pitch': initial_camera_params['pitch'] * (1 - factor) + target_camera_params['pitch'] * factor,
            'distance': initial_camera_params['distance'] * (1 - factor) + target_camera_params['distance'] * factor,
            'height': initial_camera_params['height'] * (1 - factor) + target_camera_params['height'] * factor
        }

        set_camera(
            interpolated_camera_params['position'][0],
            interpolated_camera_params['position'][1],
            interpolated_camera_params['position'][2],
            interpolated_camera_params['yaw'],
            interpolated_camera_params['pitch'],
            interpolated_camera_params['distance'],
            interpolated_camera_params['height'],
            factor
        )
    else:
        # Установка камеры без интерполяции
        set_camera(*model.position, camera_yaw, camera_pitch, camera_distance, camera_height)

    glPushMatrix()
    plane.render()
    model.render()  # Рендерим модель вместо куба
    glPopMatrix()

    glDisable(GL_DEPTH_TEST)
    interface.render_minimap(model.position[0], model.position[2], plane_size=20000)
    glEnable(GL_DEPTH_TEST)

    for event in pygame.event.get():
        if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
            running = False
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_n:  # Переключение режима noclip
            noclip_mode = not noclip_mode
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_f:  # Переключение режима камеры
            if interpolation_progress >= interpolation_duration:   # Начинаем новую интерполяцию только если предыдущая завершена
                third_person_mode = not third_person_mode

                # Сохраняем начальные параметры камеры
                initial_camera_params = {
                    'position': model.position.copy(),
                    'yaw': camera_yaw,
                    'pitch': camera_pitch,
                    'distance': camera_distance,
                    'height': camera_height
                }

                # Устанавливаем целевые параметры камеры
                if third_person_mode:
                    target_camera_params = {
                        'position': model.position.copy(),
                        'yaw': camera_yaw,
                        'pitch': camera_pitch,
                        'distance': camera_modes['third_person']['distance'],
                        'height': camera_modes['third_person']['height']
                    }
                else:
                    target_camera_params = {
                        'position': model.position.copy(),
                        'yaw': camera_yaw,
                        'pitch': camera_pitch,
                        'distance': camera_modes['first_person']['distance'],
                        'height': camera_modes['first_person']['height']
                    }

                # Сбрасываем прогресс интерполяции
                interpolation_progress = 0.0

    dx, dy = pygame.mouse.get_rel()
    camera_yaw += dx * mouse_sensitivity
    camera_pitch -= dy * mouse_sensitivity
    camera_pitch = max(min_pitch, min(max_pitch, camera_pitch))

    keys = pygame.key.get_pressed()
    current_move_speed = move_speed * 10 if noclip_mode else move_speed

    move_forward = (keys[pygame.K_w] - keys[pygame.K_s]) * current_move_speed * dt
    move_strafe = (keys[pygame.K_d] - keys[pygame.K_a]) * current_move_speed * dt

    angle = math.radians(camera_yaw)
    forward_x = math.sin(angle)
    forward_z = -math.cos(angle)
    strafe_x = math.cos(angle)
    strafe_z = math.sin(angle)

    old_model_position = model.position.copy()

    if noclip_mode:
        new_x = model.position[0] + forward_x * move_forward + strafe_x * move_strafe
        new_z = model.position[2] + forward_z * move_forward + strafe_z * move_strafe
        vertical_momentum = 0
        new_y = model.position[1] + (keys[pygame.K_SPACE] - keys[pygame.K_LCTRL]) * current_move_speed * dt
        model.update_position([new_x, new_y, new_z])
    else:
        new_x = model.position[0] + forward_x * move_forward + strafe_x * move_strafe
        new_z = model.position[2] + forward_z * move_forward + strafe_z * move_strafe

        terrain_height = plane.get_height(new_x, new_z)
        if model.position[1] < terrain_height:
            model.position[1] = terrain_height
            vertical_momentum = 0
            is_grounded = True

        if not is_grounded:
            vertical_momentum -= gravity * dt
            model.position[1] += vertical_momentum
        else:
            vertical_momentum = 0

        if is_grounded and keys[pygame.K_SPACE]:
            vertical_momentum = jump_force
            is_grounded = False

        model.update_position([new_x, model.position[1], new_z])

    pygame.display.flip()

pygame.quit()
# --- End of main.py ---