Вторая лаба
This commit is contained in:
252
lab2/main.py
Normal file
252
lab2/main.py
Normal 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()
|
||||
Reference in New Issue
Block a user