Surface Roughness
Surface Roughness Management
AsteroidShapeModels.has_roughness — Functionhas_roughness(shape::ShapeModel) -> Bool
has_roughness(shape::ShapeModel, face_idx::Int) -> BoolCheck for surface roughness on the shape model.
has_roughness(shape)checks if the shape carries any surface roughness data (i.e.,shape.roughness !== nothing).has_roughness(shape, face_idx)checks if the specified face has an associated roughness model (alwaysfalseif the shape has no roughness data at all).
Arguments
shape::ShapeModel: The shape model to checkface_idx::Int: Index of the face to check (optional)
Returns
Bool:trueif the shape (or the specified face) has surface roughness,falseotherwise
See also: add_roughness_models!, get_roughness_model
AsteroidShapeModels.get_roughness_model — Functionget_roughness_model(shape::ShapeModel, face_idx::Int) -> Union{Nothing, ShapeModel}Get the roughness model associated with a specific face.
Arguments
shape::ShapeModel: The shape modelface_idx::Int: Index of the face to query
Returns
Union{Nothing, ShapeModel}: The roughness model for the specified face, ornothingif no roughness model is associated
AsteroidShapeModels.get_roughness_model_scale — Functionget_roughness_model_scale(shape::ShapeModel, face_idx::Int) -> Float64Get the scale factor for the roughness model on a specific face.
Arguments
shape::ShapeModel: The shape modelface_idx::Int: Index of the face to query
Returns
Float64: The scale factor for the roughness model (1.0 if no roughness model)
Throws
ArgumentError: If the shape has no roughness data at all (shape.roughness === nothing)
AsteroidShapeModels.get_roughness_model_transform — Functionget_roughness_model_transform(shape::ShapeModel, face_idx::Int) -> AffineMapGet the affine transformation (global to local) for the roughness model on a specific face.
Arguments
shape::ShapeModel: The shape modelface_idx::Int: Index of the face to query
Returns
AFFINE_MAP_TYPE: The affine transformation from global to local coordinates
Throws
ArgumentError: If the shape has no roughness data at all (shape.roughness === nothing)
AsteroidShapeModels.add_roughness_models! — Functionadd_roughness_models!(
shape ::ShapeModel,
roughness_model ::ShapeModel;
scale ::Float64 = 1.0,
)Add the same surface roughness model to all faces of the shape model. On the first call, shape.roughness (a SurfaceRoughness struct) is constructed.
Arguments
shape::ShapeModel: The shape model to attach roughness toroughness_model::ShapeModel: The shape model representing the surface roughness
Keyword Arguments
scale::Float64: Scale factor for the roughness model (default: 1.0)
Throws
ArgumentError: Ifscaleis not positive, or ifroughness_modelitself has roughness data (nested roughness is not supported)
Notes
- This function applies the roughness model to ALL faces, overwriting any existing assignments.
- All faces will share the same
ShapeModelinstance, making this memory-efficient. - Appropriate transformations are automatically computed for each face using
compute_face_roughness_transform. - Use the face-specific version
add_roughness_models!(shape, roughness_model, face_idx; scale, transform)to selectively apply different models to individual faces or to provide custom transformations.
add_roughness_models!(
shape ::ShapeModel,
roughness_model ::ShapeModel,
face_idx ::Int;
scale ::Float64 = 1.0,
transform ::Union{Nothing, AFFINE_MAP_TYPE} = nothing,
)Add a surface roughness model to a specific face of the shape model. On the first call, shape.roughness (a SurfaceRoughness struct) is constructed.
Arguments
shape::ShapeModel: The shape model to attach roughness toroughness_model::ShapeModel: The shape model representing the surface roughnessface_idx::Int: Index of the face to attach the roughness to
Keyword Arguments
scale::Float64: Scale factor for the roughness model (default: 1.0)transform::Union{Nothing, AFFINE_MAP_TYPE}: Affine transformation from global to local coordinates (optional). Ifnothing(default), automatically computes an appropriate transformation usingcompute_face_roughness_transform
Throws
BoundsError: Ifface_idxis out of boundsArgumentError: Ifscaleis not positive, or ifroughness_modelitself has roughness data (nested roughness is not supported)
Notes
- If the face already has a roughness model, it will be replaced.
- When
transformisnothing, the roughness model is automatically positioned at the face center with a north-aligned local coordinate system (x: East, y: North, z: Up).
AsteroidShapeModels.clear_roughness_models! — Functionclear_roughness_models!(shape::ShapeModel)Remove all roughness models from all faces of the shape model, resetting shape.roughness to nothing (smooth surface).
Arguments
shape::ShapeModel: The shape model
clear_roughness_models!(shape::ShapeModel, face_idx::Int)Remove the roughness model from a specific face.
Arguments
shape::ShapeModel: The shape modelface_idx::Int: Index of the face to clear
Notes
- This function clears the assignment for the specified face. If the roughness model is no longer used by any face, it will be removed from memory.
- If no roughness models remain after clearing,
shape.roughnessis reset tonothing. - Does nothing if the shape has no roughness data.
Coordinate Transformations
AsteroidShapeModels.transform_point_global_to_local — Functiontransform_point_global_to_local(
shape ::ShapeModel,
face_idx ::Int,
p_global ::StaticVector{3}
) -> SVector{3, Float64}Transform a point from global to local coordinates.
Arguments
shape::ShapeModel: The shape modelface_idx::Int: Index of the face (1-based)p_global::StaticVector{3}: Point in global coordinates
Returns
SVector{3, Float64}: Point in local roughness model coordinates [0,1]×[0,1]×ℝ
Throws
ArgumentError: If the specified face has no roughness modelBoundsError: Ifface_idxis out of bounds
Notes
The local coordinate system has its origin at the face center, with:
- X-axis pointing east
- Y-axis pointing north
- Z-axis pointing up (along face normal)
The UV coordinates [0,1]×[0,1] are centered at (0.5, 0.5).
This function requires the face to have an assigned roughness model. Always check with has_roughness(shape, face_idx) before calling.
Usage
if has_roughness(shape, face_idx)
p_local = transform_point_global_to_local(shape, face_idx, p_global)
endAsteroidShapeModels.transform_point_local_to_global — Functiontransform_point_local_to_global(
shape ::ShapeModel,
face_idx ::Int,
p_local ::StaticVector{3}
) -> SVector{3, Float64}Transform a point from local to global coordinates.
Arguments
shape::ShapeModel: The shape modelface_idx::Int: Index of the face (1-based)p_local::StaticVector{3}: Point in local roughness model coordinates [0,1]×[0,1]×ℝ
Returns
SVector{3, Float64}: Point in global coordinates
Throws
ArgumentError: If the specified face has no roughness modelBoundsError: If face_idx is out of bounds
Notes
Inverse transformation of transform_point_global_to_local.
This function requires the face to have an assigned roughness model. Always check with has_roughness(shape, face_idx) before calling.
Usage
if has_roughness(shape, face_idx)
p_global = transform_point_local_to_global(shape, face_idx, p_local)
endAsteroidShapeModels.transform_geometric_vector_global_to_local — Functiontransform_geometric_vector_global_to_local(
shape ::ShapeModel,
face_idx ::Int,
v_global ::StaticVector{3}
) -> SVector{3, Float64}Transform a geometric vector (displacement, velocity) from global to local coordinates. Applies both rotation and scaling.
Arguments
shape::ShapeModel: The shape modelface_idx::Int: Index of the face (1-based)v_global::StaticVector{3}: Geometric vector in global coordinates
Returns
SVector{3, Float64}: Vector in local roughness model coordinates (scaled)
Throws
ArgumentError: If the specified face has no roughness modelBoundsError: If face_idx is out of bounds
Notes
For physical vectors (forces, torques) that should preserve magnitude, use transform_physical_vector_global_to_local instead.
This function requires the face to have an assigned roughness model. Always check with has_roughness(shape, face_idx) before calling.
Usage
if has_roughness(shape, face_idx)
v_local = transform_geometric_vector_global_to_local(shape, face_idx, v_global)
endAsteroidShapeModels.transform_geometric_vector_local_to_global — Functiontransform_geometric_vector_local_to_global(
shape ::ShapeModel,
face_idx ::Int,
v_local ::StaticVector{3}
) -> SVector{3, Float64}Transform a geometric vector (displacement, velocity) from local to global coordinates. Applies both rotation and scaling.
Arguments
shape::ShapeModel: The shape modelface_idx::Int: Index of the face (1-based)v_local::StaticVector{3}: Geometric vector in local roughness model coordinates
Returns
SVector{3, Float64}: Vector in global coordinates (scaled)
Throws
ArgumentError: If the specified face has no roughness modelBoundsError: If face_idx is out of bounds
Notes
For physical vectors (forces, torques) that should preserve magnitude, use transform_physical_vector_local_to_global instead.
This function requires the face to have an assigned roughness model. Always check with has_roughness(shape, face_idx) before calling.
Usage
if has_roughness(shape, face_idx)
v_global = transform_geometric_vector_local_to_global(shape, face_idx, v_local)
endAsteroidShapeModels.transform_physical_vector_global_to_local — Functiontransform_physical_vector_global_to_local(
shape ::ShapeModel,
face_idx ::Int,
v_global ::StaticVector{3}
) -> SVector{3, Float64}Transform a physical vector (force, torque, angular velocity) from global to local coordinates. Physical vectors are only rotated, not scaled, preserving their magnitude.
Arguments
shape::ShapeModel: The shape modelface_idx::Int: Index of the facev_global::StaticVector{3}: Physical vector in global coordinates
Returns
SVector{3, Float64}: Physical vector in local coordinate frame (not scaled)
Throws
ArgumentError: If the specified face has no roughness modelBoundsError: If face_idx is out of bounds
Notes
Use this for quantities where physical magnitude must be preserved (forces, torques, angular velocities, magnetic fields). For geometric vectors use transform_geometric_vector_global_to_local instead.
This function requires the face to have an assigned roughness model. Always check with has_roughness(shape, face_idx) before calling.
Usage
if has_roughness(shape, face_idx)
v_local = transform_physical_vector_global_to_local(shape, face_idx, v_global)
endAsteroidShapeModels.transform_physical_vector_local_to_global — Functiontransform_physical_vector_local_to_global(
shape ::ShapeModel,
face_idx ::Int,
v_local ::StaticVector{3}
) -> SVector{3, Float64}Transform a physical vector (force, torque, angular velocity) from local to global coordinates. Physical vectors are only rotated, not scaled, preserving their magnitude.
Arguments
shape::ShapeModel: The shape modelface_idx::Int: Index of the facev_local::StaticVector{3}: Physical vector in local coordinates
Returns
SVector{3, Float64}: Physical vector in global coordinate frame (not scaled)
Throws
ArgumentError: If the specified face has no roughness modelBoundsError: If face_idx is out of bounds
Notes
Use this for quantities where physical magnitude must be preserved (forces, torques, angular velocities, magnetic fields). For geometric vectors use transform_geometric_vector_local_to_global instead.
This function requires the face to have an assigned roughness model. Always check with has_roughness(shape, face_idx) before calling.
Usage
if has_roughness(shape, face_idx)
v_global = transform_physical_vector_local_to_global(shape, face_idx, v_local)
endCrater Modeling
AsteroidShapeModels.create_shape_crater — Functioncreate_shape_crater(r, h;
xc = 0.5,
yc = 0.5,
Nx = 32,
Ny = 32,
scale = 1.0,
with_face_visibility = false,
with_bvh = false,
) -> ShapeModelCreate a shape model representing a concave spherical crater.
This is a convenience wrapper that combines an internal crater-geometry helper (concave_spherical_segment_grid) and load_shape_grid. The crater sits on a unit square [0,1]×[0,1].
Arguments
r::Real: Crater radius in normalized units (0–1). A value of 0.5 fills the unit square.h::Real: Crater depth in the same units asr.
Keyword Arguments
xc::Real=0.5: x-coordinate of crater center (normalized, 0–1)yc::Real=0.5: y-coordinate of crater center (normalized, 0–1)Nx::Integer=32: Number of grid points in x-directionNy::Integer=32: Number of grid points in y-directionscale::Real=1.0: Scale factor applied to all coordinates after grid generationwith_face_visibility::Bool=false: Whether to build face-to-face visibility graphwith_bvh::Bool=false: Whether to build BVH for ray tracing
Returns
ShapeModel: Shape model with computed geometric properties (centers, normals, areas)
Example
# Crater centered at (0.5, 0.5) with radius 0.4 and depth 0.1, scaled to meters
crater = create_shape_crater(0.4, 0.1; scale=100.0, with_face_visibility=true)
# Off-center crater with higher resolution
crater = create_shape_crater(0.3, 0.1; xc=0.3, yc=0.7, Nx=64, Ny=64)
# Use as a roughness model on another shape
shape = load_shape_obj("path/to/shape.obj")
add_roughness_models!(shape, crater; scale=0.1)See also: load_shape_grid
Roughness Statistics
AsteroidShapeModels.projected_area — Functionprojected_area(shape::ShapeModel) -> A_projCompute the area of the shape projected onto the local xy-plane (i.e., along the local z-axis, which corresponds to the mean surface normal for a roughness patch):
\[A_{\mathrm{proj}} = \sum_j a_j \, (\hat{n}_j \cdot \hat{z})^+\]
where $a_j$ is the area of face $j$, $\hat{n}_j$ its unit normal, and $(x)^+ = \max(x, 0)$ so that faces tilted away from the z-axis (overhangs) do not contribute.
For a roughness patch built on the unit square $[0,1] \times [0,1]$ (e.g., create_shape_crater), the projected area equals 1 regardless of the roughness, since the z-projection of the surface covers the unit square exactly. This quantity is used to normalize energy fluxes of a representative roughness patch in thermophysical modeling.
Arguments
shape::ShapeModel: Shape model of a surface patch
Returns
A_proj::Float64: Projected area along the local z-axis
See also: rms_slope
AsteroidShapeModels.rms_slope — Functionrms_slope(shape::ShapeModel) -> θ_RMSCompute the root-mean-square (RMS) slope of a surface patch, following the standard definition in thermal-infrared beaming studies (Spencer, 1990; Rozitis & Green, 2011, Eq. 36):
\[\theta_{\mathrm{RMS}} = \sqrt{\frac{\sum_j \theta_j^2 \, a_j \cos\theta_j}{\sum_j a_j \cos\theta_j}}, \qquad \theta_j = \arccos(\hat{n}_j \cdot \hat{z})\]
where $\theta_j$ is the slope angle of face $j$ measured from the local z-axis, $a_j$ its area, and the weight $a_j \cos\theta_j$ is the projected (z-projected) area of the face. Faces tilted away from the z-axis ($\cos\theta_j \le 0$) are excluded.
Arguments
shape::ShapeModel: Shape model of a surface patch
Returns
θ_RMS::Float64: RMS slope in radians (userad2degfor degrees)
Notes
- The statistic is taken over the entire patch, including flat portions. For a crater patch built on the unit square (e.g.,
create_shape_crater), the flat apron contributes zero slope but full weight, so the roughness fraction $\sqrt{f_R}$ of Rozitis & Green (2011, Eq. 37) is automatically included. To get the RMS slope of the cratered part alone, divide by the square root of the projected areal coverage:rms_slope(shape) / √(π * r^2)for a crater of normalized radiusr(approximate on a discrete grid, where the rim discretization makes the actual coverage slightly larger thanπ * r^2). - This is not Hapke's mean slope angle $\bar{\theta}$, which uses a different (tangent-based) definition.
References
- Spencer, J. R. (1990), Icarus 83, 27
- Rozitis, B. & Green, S. F. (2011), MNRAS 415, 2042, Eqs. (36)–(37)
Example
crater = create_shape_crater(0.4, 0.4; Nx=64, Ny=64) # hemispherical crater
rad2deg(rms_slope(crater)) # ≈ 36.5° (whole patch, flat apron included)See also: projected_area