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()