feat: Implement terrain SVG export functionality via FFI and integrate it into the Arma server extension's command groups.

This commit is contained in:
Jacob Schmidt 2026-02-16 19:19:47 -06:00
parent abde6649ac
commit 082f0c6f8f
2 changed files with 185 additions and 0 deletions

View File

@ -21,6 +21,7 @@ pub mod locker;
mod log;
pub mod org;
pub mod redis;
pub mod terrain;
pub mod v_garage;
pub mod v_locker;
@ -66,6 +67,7 @@ fn init() -> Extension {
.group("icom", icom::group())
.group("locker", locker::group())
.group("org", org::group())
.group("terrain", terrain::group())
.group(
"owned",
Group::new()

View File

@ -0,0 +1,183 @@
//! Terrain SVG export functionality via FFI to external C++ library.
//!
//! Provides commands to export terrain data to SVG format with various
//! rendering options (location names, grid, contour lines, etc.).
use arma_rs::Group;
use std::ffi::CString;
use std::os::raw::{c_char, c_void};
#[cfg(target_os = "windows")]
#[link(name = "kernel32")]
unsafe extern "stdcall" {
fn GetModuleHandleA(lpModuleName: *const u8) -> *mut c_void;
fn LoadLibraryA(lpLibFileName: *const u8) -> *mut c_void;
fn GetProcAddress(hModule: *mut c_void, lpProcName: *const u8) -> *mut c_void;
}
/// Mangled C++ name for the ExportSVG function.
/// Format: `?ExportSVG@@YAXPEBD_N11111@Z` matches signature:
/// `void ExportSVG(const char*, bool, bool, bool, bool, bool, bool)`
const EXPORT_SVG_PROC_NAME: &[u8] = b"?ExportSVG@@YAXPEBD_N11111@Z\0";
/// Name of the DLL containing the ExportSVG function.
/// Update this to match your actual DLL name.
const TERRAIN_DLL_NAME: &[u8] = b"TerrainExport.dll\0";
type FnExportSVG =
extern "stdcall" fn(*const c_char, bool, bool, bool, bool, bool, bool) -> *const c_void;
/// Creates the Arma 3 command group for the terrain module.
///
/// Registers the `exportSVG` command with the Arma 3 extension.
pub fn group() -> Group {
Group::new().command("exportSVG", export_svg)
}
/// Exports terrain data to an SVG file with configurable rendering options.
///
/// # Parameters
/// - `file_path`: Output SVG file path
/// - `draw_location_names`: Include location/place names
/// - `draw_grid`: Include grid overlay
/// - `draw_contourlines`: Include elevation contour lines
/// - `draw_tree_objects`: Include vegetation/tree objects
/// - `draw_mountain_heightpoints`: Include mountain peak elevation markers
/// - `simple_roads`: Use simplified road rendering
///
/// # Returns
/// - `Ok(())` on success
/// - `Err(String)` with error message on failure
#[cfg(target_os = "windows")]
pub fn export_terrain_svg(
file_path: String,
draw_location_names: bool,
draw_grid: bool,
draw_contourlines: bool,
draw_tree_objects: bool,
draw_mountain_heightpoints: bool,
simple_roads: bool,
) -> Result<(), String> {
unsafe {
// Try to get already loaded module, or load the DLL
let module = GetModuleHandleA(TERRAIN_DLL_NAME.as_ptr());
let module = if module.is_null() {
let loaded = LoadLibraryA(TERRAIN_DLL_NAME.as_ptr());
if loaded.is_null() {
return Err(format!(
"Failed to load DLL: {}",
std::str::from_utf8_unchecked(&TERRAIN_DLL_NAME[..TERRAIN_DLL_NAME.len() - 1])
));
}
loaded
} else {
module
};
// Get the ExportSVG function address
let export_svg_proc = GetProcAddress(module, EXPORT_SVG_PROC_NAME.as_ptr());
if export_svg_proc.is_null() {
return Err("Failed to find ExportSVG function in DLL".to_string());
}
// Convert to function pointer
let export_svg: FnExportSVG = std::mem::transmute(export_svg_proc);
// Convert file path to C string
let file_path_cstr =
CString::new(file_path).map_err(|e| format!("Invalid file path: {}", e))?;
// Call the export function
export_svg(
file_path_cstr.as_ptr(),
draw_location_names,
draw_grid,
draw_contourlines,
draw_tree_objects,
draw_mountain_heightpoints,
simple_roads,
);
Ok(())
}
}
#[cfg(not(target_os = "windows"))]
pub fn export_terrain_svg(
_file_path: String,
_draw_location_names: bool,
_draw_grid: bool,
_draw_contourlines: bool,
_draw_tree_objects: bool,
_draw_mountain_heightpoints: bool,
_simple_roads: bool,
) -> Result<(), String> {
Err("Terrain SVG export is only available on Windows".to_string())
}
#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct ExportSvgOptions {
file_path: String,
#[serde(default)]
draw_location_names: bool,
#[serde(default)]
draw_grid: bool,
#[serde(default)]
draw_contourlines: bool,
#[serde(default)]
draw_tree_objects: bool,
#[serde(default)]
draw_mountain_heightpoints: bool,
#[serde(default)]
simple_roads: bool,
}
/// Arma command handler for terrain SVG export.
///
/// # SQF Usage
/// ```sqf
/// ["forge", ["terrain", "exportSVG", "{
/// ""filePath"": ""C:\\path\\to\\output.svg"",
/// ""drawLocationNames"": true,
/// ""drawGrid"": true,
/// ""drawContourlines"": true,
/// ""drawTreeObjects"": false,
/// ""drawMountainHeightpoints"": true,
/// ""simpleRoads"": false
/// }"]] call forge_fnc_callExtension;
/// ```
fn export_svg(options_json: String) -> String {
// Parse options from JSON
let options: ExportSvgOptions = match serde_json::from_str(&options_json) {
Ok(opts) => opts,
Err(e) => {
return serde_json::json!({
"status": "error",
"message": format!("Invalid JSON options: {}", e)
})
.to_string();
}
};
match export_terrain_svg(
options.file_path,
options.draw_location_names,
options.draw_grid,
options.draw_contourlines,
options.draw_tree_objects,
options.draw_mountain_heightpoints,
options.simple_roads,
) {
Ok(_) => serde_json::json!({
"status": "success",
"message": "Terrain exported successfully"
})
.to_string(),
Err(e) => serde_json::json!({
"status": "error",
"message": e
})
.to_string(),
}
}