///|
/// Configuration for rendering a 3D synthetic marker scene.
pub(all) struct SyntheticSceneConfig {
image_size : Size2D
camera : CameraIntrinsics
marker_id : Int
dictionary : MarkerDictionary
marker_length : Double
pose : Pose3D
noise_std : Double
lighting_ambient : Double
lighting_diffuse : Double
} derive(Debug, Eq)
///|
/// Creates default synthetic scene configuration.
pub fn SyntheticSceneConfig::default(
marker_id : Int,
dictionary : MarkerDictionary,
) -> SyntheticSceneConfig {
let size = Size2D::new(640, 480)
let cam = CameraIntrinsics::new(500.0, 500.0, 320.0, 240.0)
let pose = Pose3D::new(
Point3D::new(0.0, 0.0, 0.0),
Point3D::new(0.0, 0.0, 0.5),
)
{
image_size: size,
camera: cam,
marker_id,
dictionary,
marker_length: 0.1,
pose,
noise_std: 0.0,
lighting_ambient: 0.8,
lighting_diffuse: 0.2,
}
}
///|
/// Renders a full 3D synthetic marker scene onto a camera image plane.
pub fn render_synthetic_scene(config : SyntheticSceneConfig) -> Image {
let w = config.image_size.width
let h = config.image_size.height
let canvas = Image::new(w, h)
canvas.fill(200) // Light gray background
// Generate marker texture
let marker_tex_size = 200
let marker_tex = match
generate_synthetic_marker_image(
config.marker_id,
config.dictionary,
marker_tex_size,
0,
) {
Some(img) => img
None => return canvas
}
// 3D Corner coordinates of marker on board plane (Z = 0)
let half_s = config.marker_length * 0.5
// Camera/image coordinates use Y-down, so the image top edge is -Y.
let c_tl = Point3D::new(-half_s, -half_s, 0.0)
let c_tr = Point3D::new(half_s, -half_s, 0.0)
let c_br = Point3D::new(half_s, half_s, 0.0)
let c_bl = Point3D::new(-half_s, half_s, 0.0)
// Project 3D corners onto 2D image plane
let p_tl = project_3d_point(c_tl, config.pose, config.camera)
let p_tr = project_3d_point(c_tr, config.pose, config.camera)
let p_br = project_3d_point(c_br, config.pose, config.camera)
let p_bl = project_3d_point(c_bl, config.pose, config.camera)
let quad = Quad::new(p_tl, p_tr, p_br, p_bl)
let dst_corners = [
Point2D::new(0.0, 0.0),
Point2D::new(marker_tex_size.to_double(), 0.0),
Point2D::new(marker_tex_size.to_double(), marker_tex_size.to_double()),
Point2D::new(0.0, marker_tex_size.to_double()),
]
// Homography mapping canvas quad -> marker texture.
// find_homography returns the source-to-destination matrix, which is
// exactly the canvas-to-texture mapping required by this rasterizer.
match find_homography(quad.corners, dst_corners) {
Some(canvas_to_texture) => {
let bbox = quad.bounding_rect()
let min_x = if bbox.x < 0.0 { 0 } else { bbox.x.floor().to_int() }
let min_y = if bbox.y < 0.0 { 0 } else { bbox.y.floor().to_int() }
let max_x = if bbox.x + bbox.width >= w.to_double() {
w - 1
} else {
(bbox.x + bbox.width).ceil().to_int()
}
let max_y = if bbox.y + bbox.height >= h.to_double() {
h - 1
} else {
(bbox.y + bbox.height).ceil().to_int()
}
let poly = Polygon2D::new(quad.corners)
for y in min_y..<=max_y {
for x in min_x..<=max_x {
let pt = Point2D::new(x.to_double() + 0.5, y.to_double() + 0.5)
if poly.contains_point(pt) {
let tex_pt = canvas_to_texture.transform_point2d(pt)
if tex_pt.x >= 0.0 &&
tex_pt.x < marker_tex_size.to_double() &&
tex_pt.y >= 0.0 &&
tex_pt.y < marker_tex_size.to_double() {
let px = marker_tex.sample_bilinear(tex_pt)
let lit_px = (px * config.lighting_ambient).round().to_int()
canvas.set(x, y, lit_px)
}
}
}
}
}
None => ()
}
// Optional Gaussian noise
if config.noise_std > 0.0 {
let mut rng_state = 12345L
let next_gaussian = fn() -> Double {
rng_state = rng_state * 6364136223846793005L + 1442695040888963407L
let u1 = ((rng_state & 0x7fffffffL).to_double() + 1.0) / 2147483648.0
rng_state = rng_state * 6364136223846793005L + 1442695040888963407L
let u2 = ((rng_state & 0x7fffffffL).to_double() + 1.0) / 2147483648.0
(-2.0 * @math.ln(u1)).sqrt() * @math.cos(2.0 * 3.141592653589793 * u2)
}
for y in 0..