Surface Roughness

Surface Roughness Management

AsteroidShapeModels.has_roughnessFunction
has_roughness(shape::ShapeModel) -> Bool
has_roughness(shape::ShapeModel, face_idx::Int) -> Bool

Check 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 (always false if the shape has no roughness data at all).

Arguments

  • shape::ShapeModel : The shape model to check
  • face_idx::Int : Index of the face to check (optional)

Returns

  • Bool : true if the shape (or the specified face) has surface roughness, false otherwise

See also: add_roughness_models!, get_roughness_model

source
AsteroidShapeModels.get_roughness_modelFunction
get_roughness_model(shape::ShapeModel, face_idx::Int) -> Union{Nothing, ShapeModel}

Get the roughness model associated with a specific face.

Arguments

  • shape::ShapeModel : The shape model
  • face_idx::Int : Index of the face to query

Returns

  • Union{Nothing, ShapeModel} : The roughness model for the specified face, or nothing if no roughness model is associated
source
AsteroidShapeModels.get_roughness_model_scaleFunction
get_roughness_model_scale(shape::ShapeModel, face_idx::Int) -> Float64

Get the scale factor for the roughness model on a specific face.

Arguments

  • shape::ShapeModel : The shape model
  • face_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)
source
AsteroidShapeModels.get_roughness_model_transformFunction
get_roughness_model_transform(shape::ShapeModel, face_idx::Int) -> AffineMap

Get the affine transformation (global to local) for the roughness model on a specific face.

Arguments

  • shape::ShapeModel : The shape model
  • face_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)
source
AsteroidShapeModels.add_roughness_models!Function
add_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 to
  • roughness_model::ShapeModel : The shape model representing the surface roughness

Keyword Arguments

  • scale::Float64 : Scale factor for the roughness model (default: 1.0)

Throws

  • ArgumentError : If scale is not positive, or if roughness_model itself 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 ShapeModel instance, 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.
source
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 to
  • roughness_model::ShapeModel : The shape model representing the surface roughness
  • face_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). If nothing (default), automatically computes an appropriate transformation using compute_face_roughness_transform

Throws

  • BoundsError : If face_idx is out of bounds
  • ArgumentError : If scale is not positive, or if roughness_model itself has roughness data (nested roughness is not supported)

Notes

  • If the face already has a roughness model, it will be replaced.
  • When transform is nothing, the roughness model is automatically positioned at the face center with a north-aligned local coordinate system (x: East, y: North, z: Up).
source
AsteroidShapeModels.clear_roughness_models!Function
clear_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
source
clear_roughness_models!(shape::ShapeModel, face_idx::Int)

Remove the roughness model from a specific face.

Arguments

  • shape::ShapeModel : The shape model
  • face_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.roughness is reset to nothing.
  • Does nothing if the shape has no roughness data.
source

Coordinate Transformations

AsteroidShapeModels.transform_point_global_to_localFunction
transform_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 model
  • face_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 model
  • BoundsError : If face_idx is 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).

Requires roughness model

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)
end
source
AsteroidShapeModels.transform_point_local_to_globalFunction
transform_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 model
  • face_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 model
  • BoundsError : If face_idx is out of bounds

Notes

Inverse transformation of transform_point_global_to_local.

Requires roughness model

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)
end
source
AsteroidShapeModels.transform_geometric_vector_global_to_localFunction
transform_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 model
  • face_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 model
  • BoundsError : 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.

Requires roughness model

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)
end
source
AsteroidShapeModels.transform_geometric_vector_local_to_globalFunction
transform_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 model
  • face_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 model
  • BoundsError : 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.

Requires roughness model

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)
end
source
AsteroidShapeModels.transform_physical_vector_global_to_localFunction
transform_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 model
  • face_idx::Int : Index of the face
  • v_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 model
  • BoundsError : 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.

Requires roughness model

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)
end
source
AsteroidShapeModels.transform_physical_vector_local_to_globalFunction
transform_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 model
  • face_idx::Int : Index of the face
  • v_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 model
  • BoundsError : 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.

Requires roughness model

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)
end
source

Crater Modeling

AsteroidShapeModels.create_shape_craterFunction
create_shape_crater(r, h;
    xc = 0.5,
    yc = 0.5,
    Nx = 32,
    Ny = 32,
    scale = 1.0,
    with_face_visibility = false,
    with_bvh = false,
) -> ShapeModel

Create 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 as r.

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-direction
  • Ny::Integer=32 : Number of grid points in y-direction
  • scale::Real=1.0 : Scale factor applied to all coordinates after grid generation
  • with_face_visibility::Bool=false : Whether to build face-to-face visibility graph
  • with_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

source

Roughness Statistics

AsteroidShapeModels.projected_areaFunction
projected_area(shape::ShapeModel) -> A_proj

Compute 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

source
AsteroidShapeModels.rms_slopeFunction
rms_slope(shape::ShapeModel) -> θ_RMS

Compute 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 (use rad2deg for 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 radius r (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

source