Files
computer-graphics/lab2/main.py
2025-09-12 18:19:12 +03:00

253 lines
9.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import math
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
def rotate_points(points, angle_x, angle_y, angle_z):
# Матрицы поворота
rx = np.array(
[
[1, 0, 0],
[0, math.cos(angle_x), -math.sin(angle_x)],
[0, math.sin(angle_x), math.cos(angle_x)],
]
)
ry = np.array(
[
[math.cos(angle_y), 0, math.sin(angle_y)],
[0, 1, 0],
[-math.sin(angle_y), 0, math.cos(angle_y)],
]
)
rz = np.array(
[
[math.cos(angle_z), -math.sin(angle_z), 0],
[math.sin(angle_z), math.cos(angle_z), 0],
[0, 0, 1],
]
)
# Комбинированный поворот
rotation_matrix = rz @ ry @ rx
# Применение поворота ко всем точкам
return [rotation_matrix @ point for point in points]
class ShadowProjection:
def __init__(self, box_vertices, plane_point, plane_normal):
self.box_vertices = np.array(box_vertices)
self.plane_point = np.array(plane_point)
self.plane_normal = np.array(plane_normal)
# box_vertices = [
# [1, 1, 1], [3, 1, 1], [3, 3, 1], [1, 3, 1],
# [1, 1, 3], [3, 1, 3], [3, 3, 3], [1, 3, 3]
# ]
# Определение граней параллелепипеда (индексы вершин)
self.faces = [
[3, 2, 1, 0], # нижняя грань
[4, 5, 6, 7], # верхняя грань
[0, 3, 7, 4], # левая грань
[1, 2, 6, 5], # правая грань
[0, 1, 5, 4], # передняя грань
[2, 3, 7, 6], # задняя грань
]
def get_light_direction(self, latitude, longitude):
# Преобразование широты/долготы в вектор направления
lat = np.radians(latitude)
lon = np.radians(longitude)
return np.array(
[np.cos(lat) * np.cos(lon), np.cos(lat) * np.sin(lon), np.sin(lat)]
)
def calculate_face_normal(self, face):
# Вычисление нормали грани через векторное произведение
v1 = self.box_vertices[face[1]] - self.box_vertices[face[0]]
v2 = self.box_vertices[face[2]] - self.box_vertices[face[0]]
normal = np.cross(v1, v2)
return normal / np.linalg.norm(normal)
def project_shadow(self, light_dir):
# 1. Найти нелицевые грани
back_faces = []
for i, face in enumerate(self.faces):
normal = self.calculate_face_normal(face)
if np.dot(normal, light_dir) <= 0:
back_faces.append(face)
# 2. Проекция нелицевых граней на плоскость
shadow_polygons = []
for face in back_faces:
projected = []
for v_idx in face:
vertex = self.box_vertices[v_idx]
# Параллельная проекция на плоскость
t = np.dot(self.plane_normal, self.plane_point - vertex) / np.dot(
self.plane_normal, light_dir
)
shadow_point = vertex + t * light_dir
projected.append(shadow_point)
shadow_polygons.append(projected)
return shadow_polygons
def visualize(self, light_dir, observer_pos):
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection="3d")
# Создаем поверхность (плоскость)
x = np.linspace(-12, 12, 20)
y = np.linspace(-12, 12, 20)
X, Y = np.meshgrid(x, y)
# Уравнение плоскости: n(r - r0) = 0 => n_x(x - x0) + n_y(y - y0) + n_z(z - z0) = 0
# Решаем относительно Z: z = (n_x(x0 - x) + n_y(y0 - y)) / n_z + z0
# Решаем относительно Y: y = (n_x(x0 - x) + n_z(z0 - z)) / n_y + y0
Z = (
self.plane_normal[0] * (self.plane_point[0] - X)
+ self.plane_normal[1] * (self.plane_point[1] - Y)
) / self.plane_normal[2] + self.plane_point[2]
# Отрисовка плоскости
ax.plot_surface(X, Y, Z, alpha=0.3, color="yellow")
# Отрисовка параллелепипеда
ax.add_collection3d(
Poly3DCollection(
[self.box_vertices[face] for face in self.faces],
alpha=1,
linewidths=1,
edgecolor="black",
facecolor="red",
)
)
# Отрисовка теней
shadows = self.project_shadow(light_dir)
ax.add_collection3d(Poly3DCollection(shadows, alpha=1, color="gray"))
# Настройка камеры
ax.view_init(elev=observer_pos[0], azim=observer_pos[1])
ax.set_proj_type("ortho")
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_xlim3d(-12, 12)
ax.set_ylim3d(-12, 12)
ax.set_zlim3d(0, 12)
plt.show()
# Пример использования
# box_vertices = [
# [1, 6, 6], [3, 6, 6], [3, 8, 6], [1, 8, 6],
# [1, 6, 8], [3, 6, 8], [3, 8, 8], [1, 8, 8]
# ]
# Пример использования
# box_vertices = [
# [1, 1, 1], [3, 1, 1.5], [3.5, 3, 1.5], [1.5, 3, 1],
# [1, 1.5, 3], [3, 1, 3.5], [3.5, 3, 4], [1.5, 3.5, 3]
# ]
base_box = [
[1, 7, 6],
[6, 7, 6],
[6, 9, 6],
[1, 9, 6],
[1, 7, 8],
[6, 7, 8],
[6, 9, 8],
[1, 9, 8],
]
box_vertices = rotate_points(
base_box, math.radians(30), math.radians(30), math.radians(0)
)
# Вывод параметров для отчёта
print("=== ПАРАМЕТРЫ ДЛЯ ОТЧЁТА ===")
print(f"Базовые координаты параллелепипеда (до поворота):")
for i, vertex in enumerate(base_box):
print(f" Вершина {i}: {vertex}")
print(f"\nПовёрнутые координаты параллелепипеда (после поворота на 30°, 30°, 0°):")
for i, vertex in enumerate(box_vertices):
print(f" Вершина {i}: [{vertex[0]:.2f}, {vertex[1]:.2f}, {vertex[2]:.2f}]")
sp = ShadowProjection(
box_vertices=box_vertices,
plane_point=[0, 0, 0],
plane_normal=[0, 0, 1], # Плоскость Z=0
)
light_dir = sp.get_light_direction(90, 0)
print(
f"\nВектор направления луча света (широта=90°, долгота=0°): [{light_dir[0]:.3f}, {light_dir[1]:.3f}, {light_dir[2]:.3f}]"
)
print(f"Точка плоскости: [0, 0, 0]")
print(f"Нормаль плоскости: [0, 0, 1]")
print(f"Позиция наблюдателя (elevation=90°, azimuth=0°): (90, 0)")
print("========================\n")
def generate_report_images():
"""Генерация изображений для отчёта с разных ракурсов"""
# Три разных ракурса для отчёта
viewpoints = [
(90, 0, "Вид сверху (elevation=90°, azimuth=0°)"),
(60, 180, "Вид с противоположной стороны (elevation=60°, azimuth=180°)"),
(30, 45, "Вид под углом (elevation=30°, azimuth=45°)"),
]
for i, (elev, azim, description) in enumerate(viewpoints, 1):
print(f"Генерируем рисунок {i+1}: {description}")
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection="3d")
# Создаем поверхность (плоскость)
x = np.linspace(-12, 12, 20)
y = np.linspace(-12, 12, 20)
X, Y = np.meshgrid(x, y)
Z = (
sp.plane_normal[0] * (sp.plane_point[0] - X)
+ sp.plane_normal[1] * (sp.plane_point[1] - Y)
) / sp.plane_normal[2] + sp.plane_point[2]
# Отрисовка плоскости
ax.plot_surface(X, Y, Z, alpha=0.3, color="yellow")
# Отрисовка параллелепипеда
ax.add_collection3d(
Poly3DCollection(
[sp.box_vertices[face] for face in sp.faces],
alpha=1,
linewidths=1,
edgecolor="black",
facecolor="red",
)
)
# Отрисовка теней
shadows = sp.project_shadow(light_dir)
ax.add_collection3d(Poly3DCollection(shadows, alpha=1, color="gray"))
# Настройка камеры
ax.view_init(elev=elev, azim=azim)
ax.set_proj_type("ortho")
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_xlim3d(-12, 12)
ax.set_ylim3d(-12, 12)
ax.set_zlim3d(0, 12)
# Сохранение изображения без заголовка
plt.savefig(f"report/img/figure_{i+1}.png", dpi=300, bbox_inches="tight")
plt.show()
print(f"Изображение сохранено как: report/img/figure_{i+1}.png")
# Запуск генерации изображений
generate_report_images()