253 lines
9.1 KiB
Python
253 lines
9.1 KiB
Python
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()
|