# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2025 SWGY, Inc
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import matplotlib.pyplot as plt
def visualize_geometry(
geometry: List[trimesh.base.Trimesh],
blast_point: np.ndarray,
sensor: Sensor
):
"""
Visualize the simulation geometry, blast point, and sensor.
Args:
geometry: List of trimesh meshes
blast_point: Blast source coordinate
sensor: Sensor object
"""
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# Plot meshes
for i, mesh in enumerate(geometry):
faces = mesh.faces
vertices = mesh.vertices
ax.plot_trisurf(
vertices[:, 0], vertices[:, 1], vertices[:, 2],
triangles=faces, alpha=0.5, color=f'C{i}'
)
# Plot blast point
ax.scatter(
blast_point[0], blast_point[1], blast_point[2],
color='red', s=100, label='Blast Point'
)
# Plot sensor
u, v = np.mgrid[0:2*np.pi:20j, 0:np.pi:10j]
x = sensor.radius * np.cos(u) * np.sin(v) + sensor.origin[0]
y = sensor.radius * np.sin(u) * np.sin(v) + sensor.origin[1]
z = sensor.radius * np.cos(v) + sensor.origin[2]
ax.plot_surface(x, y, z, color='blue', alpha=0.3)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('Simulation Geometry')
ax.legend()
plt.tight_layout()
plt.show()
def create_simulation_scene(
geometry: List[trimesh.base.Trimesh],
blast_point: np.ndarray,
sensor: Sensor,
extents: Extents,
ray_length: float = 2.0,
extent_resolution: int = 8
) -> trimesh.Scene:
"""
Create a trimesh.Scene visualizing the simulation setup.
Args:
geometry: List of trimesh meshes.
blast_point: Blast source coordinate.
sensor: Sensor object.
extents: Angular extents in radians.
ray_length: Length of rays marking extents.
extent_resolution: Number of rays per edge to indicate extents.
Returns:
trimesh.Scene object containing all visualization elements.
"""
scene = trimesh.Scene()
# Add geometry to scene
for mesh in geometry:
scene.add_geometry(mesh)
# Add sensor visualization (blue sphere)
sensor_sphere = trimesh.creation.icosphere(radius=sensor.radius, subdivisions=3)
sensor_sphere.visual.face_colors = [0, 0, 255, 100] # Semi-transparent blue
sensor_sphere.apply_translation(sensor.origin)
scene.add_geometry(sensor_sphere)
# Add blast point visualization (yellow sphere)
blast_sphere = trimesh.creation.icosphere(radius=0.05 * sensor.radius, subdivisions=2)
blast_sphere.visual.face_colors = [255, 255, 0, 255] # Opaque yellow
blast_sphere.apply_translation(blast_point)
scene.add_geometry(blast_sphere)
# Coordinate frame based on sensor and blast point
forward, right, up = create_coordinate_frame(sensor.origin - blast_point)
# Generate rays marking the angular extents (red rays)
azimuths = np.linspace(extents.left, extents.right, extent_resolution)
elevations = np.linspace(extents.bottom, extents.top, extent_resolution)
ray_origins = []
ray_ends = []
for az in azimuths:
for el in [extents.top, extents.bottom]:
direction = (
forward * np.cos(el) * np.cos(az) +
right * np.cos(el) * np.sin(az) +
up * np.sin(el)
)
direction = unit_vector(direction)
ray_origins.append(blast_point)
ray_ends.append(blast_point + direction * ray_length)
for el in elevations:
for az in [extents.left, extents.right]:
direction = (
forward * np.cos(el) * np.cos(az) +
right * np.cos(el) * np.sin(az) +
up * np.sin(el)
)
direction = unit_vector(direction)
ray_origins.append(blast_point)
ray_ends.append(blast_point + direction * ray_length)
# Create lines from rays
for origin, end in zip(ray_origins, ray_ends):
ray_line = trimesh.load_path(np.vstack([origin, end]))
ray_line.colors = [(255, 0, 0, 255)] # Red
scene.add_geometry(ray_line)
return scene