import sys
from direct.showbase.ShowBase import ShowBase
from panda3d.core import AmbientLight, DirectionalLight, Vec4, Vec3, CollisionHandlerPusher, CollisionNode, CollisionSphere, NodePath

class MyGame(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        # 1. Настройка окна и мыши
        self.set_window_title("Моя Простая 3D Игра")
        self.disable_mouse()  # Отключаем управление камерой по умолчанию мышью

        # 2. Настройка освещения
        self.setup_lights()

        # 3. Настройка окружения (земля и препятствия)
        self.setup_environment()

        # 4. Настройка игрока
        self.setup_player()

        # 5. Настройка камеры
        self.setup_camera()

        # 6. Настройка ввода (управление игроком)
        self.setup_input()

        # 7. Добавление основной задачи (обновление игры каждый кадр)
        self.task_mgr.add(self.update_game, "update_game_task")

        # Для отладки: показать FPS
        self.set_frame_rate_meter(True)

    def setup_lights(self):
        # Добавляем фоновый свет (освещает все равномерно)
        ambient_light = AmbientLight("ambientLight")
        ambient_light.set_color(Vec4(0.6, 0.6, 0.6, 1)) # Немного сероватый свет
        ambient_light_np = self.render.attach_new_node(ambient_light)
        self.render.set_light(ambient_light_np)

        # Добавляем направленный свет (имитирует солнце)
        directional_light = DirectionalLight("directionalLight")
        directional_light.set_color(Vec4(0.8, 0.8, 0.8, 1)) # Яркий белый свет
        directional_light_np = self.render.attach_new_node(directional_light)
        # Устанавливаем направление света (сверху-сзади-справа)
        directional_light_np.set_hpr(30, -60, 0)
        self.render.set_light(directional_light_np)

    def setup_environment(self):
        # Земля: большой плоский куб (можно использовать модель plane, но куб проще для примера)
        self.ground = self.loader.load_model("models/cube") # Panda3D поставляется с базовыми моделями
        self.ground.set_scale(100, 100, 0.1) # Сделать его очень плоским и широким
        self.ground.set_pos(0, 0, -1) # Расположить чуть ниже нуля
        self.ground.reparent_to(self.render)
        self.ground.set_color(0.3, 0.6, 0.3, 1) # Зеленый цвет

        # Препятствия: несколько кубов
        obstacle1 = self.loader.load_model("models/cube")
        obstacle1.set_scale(2)
        obstacle1.set_pos(10, 10, 0)
        obstacle1.reparent_to(self.render)
        obstacle1.set_color(0.8, 0.2, 0.2, 1) # Красный

        obstacle2 = self.loader.load_model("models/cube")
        obstacle2.set_scale(3, 1, 2)
        obstacle2.set_pos(-5, 15, 0.5)
        obstacle2.reparent_to(self.render)
        obstacle2.set_color(0.2, 0.2, 0.8, 1) # Синий

        obstacle3 = self.loader.load_model("models/cube")
        obstacle3.set_scale(1, 4, 1.5)
        obstacle3.set_pos(-15, -10, 0.25)
        obstacle3.reparent_to(self.render)
        obstacle3.set_color(0.8, 0.8, 0.2, 1) # Желтый

        self.obstacles = [obstacle1, obstacle2, obstacle3]

    def setup_player(self):
        # Игрок: куб
        self.player = self.loader.load_model("models/cube")
        self.player.set_scale(1) # Размер игрока
        self.player.set_pos(0, 0, 0) # Начальная позиция
        self.player.reparent_to(self.render)
        self.player.set_color(0.2, 0.8, 0.8, 1) # Голубой

        # Настройка коллизий для игрока
        # Создаем узел коллизий для игрока
        player_collision_node = CollisionNode("player_collider")
        # Добавляем сферу коллизий к этому узлу. Радиус 0.75, центр (0,0,0) относительно игрока
        player_collision_node.add_solid(CollisionSphere(0, 0, 0, 0.75))
        # Прикрепляем узел коллизий к игроку
        self.player_collider = self.player.attach_new_node(player_collision_node)
        # self.player_collider.show() # Раскомментируйте для визуализации сферы коллизий

        # Обработчик коллизий: "толкатель", который не позволит проходить сквозь объекты
        self.pusher = CollisionHandlerPusher()
        self.pusher.add_collider(self.player_collider, self.player)

        # Регистрация коллизий в Panda3D
        self.cTrav.add_collider(self.player_collider, self.pusher)

        # Добавляем коллизии для препятствий (для корректной работы pusher)
        for obstacle in self.obstacles:
            obstacle_collision_node = CollisionNode("obstacle_collider")
            # Сферы коллизий вокруг каждого препятствия (можно использовать CollisionBox для более точной формы)
            # Для простоты, используем сферу, подбирая радиус
            # Если obstacle.get_scale() возвращает Vec3, берем наибольший компонент
            scale = obstacle.get_scale()
            max_scale = max(scale.x, scale.y, scale.z)
            obstacle_collision_node.add_solid(CollisionSphere(0, 0, 0, max_scale * 0.75))
            # Прикрепляем узел коллизий к препятствию
            obstacle.attach_new_node(obstacle_collision_node)


    def setup_camera(self):
        # Привязываем камеру к игроку, чтобы она следовала за ним
        self.camera.reparent_to(self.player)
        # Устанавливаем позицию камеры относительно игрока (сзади, сверху)
        self.camera.set_pos(0, -10, 5)
        # Направляем камеру на игрока
        self.camera.look_at(self.player)

    def setup_input(self):
        # Словарь для отслеживания состояния клавиш
        self.key_map = {
            "w": False, "s": False, "a": False, "d": False
        }
        # Скорость движения игрока
        self.player_speed = 5.0 # юнитов в секунду
        self.turn_speed = 90.0 # градусов в секунду

        # Привязываем клавиши к функциям
        self.accept("w", self.set_key, ["w", True])
        self.accept("w-up", self.set_key, ["w", False])
        self.accept("s", self.set_key, ["s", True])
        self.accept("s-up", self.set_key, ["s", False])
        self.accept("a", self.set_key, ["a", True])
        self.accept("a-up", self.set_key, ["a", False])
        self.accept("d", self.set_key, ["d", True])
        self.accept("d-up", self.set_key, ["d", False])

        # Клавиша Esc для выхода из игры
        self.accept("escape", sys.exit)

    def set_key(self, key, value):
        # Функция для установки состояния клавиши в key_map
        self.key_map[key] = value

    def update_game(self, task):
        # Получаем время, прошедшее с последнего кадра (для независимости от FPS)
        dt = globalClock.get_dt()

        # 8. Логика движения игрока
        # Движение вперед/назад
        if self.key_map["w"]:
            self.player.set_y(self.player, self.player_speed * dt)
        if self.key_map["s"]:
            self.player.set_y(self.player, -self.player_speed * dt)

        # Повороты влево/вправо
        if self.key_map["a"]:
            self.player.set_h(self.player, self.turn_speed * dt)
        if self.key_map["d"]:
            self.player.set_h(self.player, -self.turn_speed * dt)

        # Возвращаем task.cont, чтобы задача продолжала выполняться каждый кадр
        return task.cont

# Запуск игры
if __name__ == "__main__":
    game = MyGame()
    game.run()