Вторая лаба

This commit is contained in:
2025-09-12 15:08:37 +03:00
parent d85c3a36a6
commit 6f3b5c3cc5
13 changed files with 746 additions and 1 deletions

252
lab2/main.py Normal file
View File

@@ -0,0 +1,252 @@
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()