farthing makes far things small. It builds the LODs, proxies and impostors that stand in for what's in the distance, and it keeps them looking like the original from where they're seen. A farthing was the smallest coin. A little goes a long way.
It's a pure-Rust, engine-agnostic asset optimizer. It has no unsafe code, gives the same
output on any machine and any thread count, and is MIT-0 licensed. Use it as a library, a CLI
or a build step.
farthing's reducer protects what you actually see: silhouettes, UV seams, shading normals and texture layout. This is the widest gap of the 24 image comparisons. Across all 24, farthing is ahead in 21, by +1.5 dB on average (every case).
Measured over 8 assets at 50%, 20%, 5% and 1% of their triangles. The price is speed: farthing is 5–9× slower than meshoptimizer because it measures error much more carefully. That's meant for a build step, not for every frame. A fast mode gets within 2× of meshoptimizer's speed and still has lower distance and UV error (normals are slightly worse). Methodology and per-asset tables are in docs/BENCHMARKS.md.
farthing measures each level's worst-case deviation from the original and turns it into a switch distance: the point where that error is one pixel on a 1080p screen. You don't have to tune distances by eye. The farthest level here is a baked proxy, one new mesh with a new UV atlas and colour, normal and roughness/metal textures baked from the original. Most of its difference is the glass visor, which an opaque proxy can't reproduce.
Far levels need a fresh UV atlas to bake into. Against xatlas at its best settings, with
both packed into the same 1024² atlas, farthing makes fewer charts with shorter seams on
all 8 benchmark assets and uses more of the texture on 7 (67% on average, against 59%).
Stretch is a near tie: farthing is lower on 5 assets, and every result is within 1% of
isometric (data). The packer is available on its own as
snugly.
Past a hundred pixels or so, a whole model becomes one card. farthing plans the views, your engine renders them with its real materials, and farthing assembles a relightable atlas sized for one texel per screen pixel. It ships with a reference shader that blends between views and marches depth for parallax.
| What it does | How | |
|---|---|---|
| Reduction | LODs by ratio, triangle count, error bound or screen size, with a switch distance for each level | Attribute-aware quadric edge collapse (UVs, colours, normals, skin weights, morph targets), silhouette-weighted, with batched parallel rounds. UV seams are permissive or strict. Whole parts are removed when they stop being visible. Symmetry is detected and preserved. Includes a fast mode |
| Quad reduction | Lowers quad meshes and keeps them all quads | Removes edge rings, ranked by quadric error, in linear time |
| Proxies | Rebuilds a messy kitbash or scene as one closed, manifold mesh | Voxelization with sub-voxel signed boundaries, leak-aware gap sealing, surface nets, then reduction |
| UV atlases | Fresh atlases for proxies and far LODs | LSCM + ARAP charts with exact overlap tests, split and merge under stretch limits, packed by snugly |
| Baking | Moves colour, normal, roughness/metal and any custom channel onto the new mesh | farthing finds where each texel comes from on the source surface, and your engine shades it. Supersampled, dilated, MikkTSpace normals. Has a two-phase API for async GPUs |
| Cluster LOD | Nanite-style cluster DAGs for per-cluster LOD on the GPU, crack-free for any camera | Meshlets (128 triangles, 255 vertices) grouped by shared border, each group reduced to half with its border locked, levels down to one root. Errors and bounds grow monotonically, so the select test runs per cluster with no traversal. Parallel, deterministic |
| HLOD | Turns a whole area (buildings, props, terrain pieces) into one distant proxy with one atlas, or a whole level into a tree of nested cells | Camera-sampled hidden-surface removal, small parts kept whole, merge, then reduction or voxel remesh, unwrap, and a bake from the visible source only. Loose-octree cells, each parent built from its children, switch distances per node and a selection that covers every instance exactly once |
| Aggregation | Merges many objects into one draw call | Material classes, island repacking by texel density, texel copy |
| Impostors | Cards for the far distance | Octahedral, hemi-octahedral, flipbook or single view, with an optional second depth layer, billboard clouds and progressive surfel clouds, which also export as 3D Gaussian splats (.ply) shaped to the surface each one stands for. Reference WGSL included |
| Visibility & occluders | Removes what can't be seen and builds occlusion geometry | Camera-sampled ID renders (alpha-aware through your engine), plus conservative self-tuning occluder meshes and boxes |
| Bones | Fewer joints and influences for far LODs | Leaf-first removal by skinned error, influence limiting |
| I/O | glTF/GLB (skins, morphs, embedded textures) and OBJ | Optional io feature |
farthing never decides what anything looks like. It handles geometry, UVs,
correspondence and packing. Anything that needs real materials goes through a small
RenderHost trait that your engine implements. That covers baking, impostor captures and
alpha-aware visibility. So world-space, procedural and engine-specific materials all bake
correctly. engine-poc/ is a minimal wgpu engine that proves the boundary
with procedural materials farthing never sees.
In the browser. Open the live demo (source in
demo-web/). It runs the whole pipeline in WebAssembly. Drop a
glTF, watch the LOD chain build and bake, and fly out to see the levels switch. Nothing is
uploaded.
From the command line. Grab a prebuilt binary from Releases, or build it:
cargo install farthing-cli
farthing lods model.glb -o lods/ --levels 4 --far-from 3 # LOD chain, far levels baked
farthing reduce model.glb -o lod.glb --ratio 0.25 # one level
farthing reduce model.glb -o lod.glb --ratio 0.25 --strict-seams --symmetry
farthing remesh scene.glb -o proxy.glb --resolution 128 --triangles 3000
farthing uv far.glb -o far_uv.glb
farthing occluder scene.glb -o occ.glb --triangles 300 --boxes 8
farthing bones character.glb -o char_lod.glb --ratio 0.5 --influences 2
farthing quad-reduce character.obj -o character_lod.obj --ratio 0.25
farthing clusters model.glb -o dag.json # Nanite-style cluster DAG
farthing hlod block.glb -o far.glb --triangles 2000 --hemisphere # distant proxy of an area
farthing hlod level.glb -o hlod/ --cells 64 --hemisphere # nested HLOD cells + hlod.json
farthing splats tree.glb -o tree.ply --count 20000 # foliage as Gaussian splats (--round: old fixed discs)As a library:
[dependencies]
farthing = "0.15" # with glTF/OBJ I/O
# farthing = { version = "0.15", default-features = false } # geometry only| Integration level | How | Use when |
|---|---|---|
| Geometry only | Call simplify, remesh, occluder, parameterize and others on Mesh/Scene values |
Offline tools and build steps with no material needs |
RenderHost |
Implement the five-method trait (example: engine-poc/src/engine.rs) |
Baking, impostors and visibility with your engine's real materials |
| Two-phase bake | prepare_bake → shade the samples on your GPU, any thread, any timing → finish_bake |
Engines that render asynchronously or batch GPU work |
| Files + CLI | glTF/OBJ in, glTF/OBJ and JSON reports out | Asset pipelines, CI, non-Rust tooling |
farthing is a set of functions over two data types. Each function takes plain data, returns
plain data and keeps no global state, so you can call them in any order, from any thread. This
section goes through them in roughly the order an asset pipeline uses them. Every snippet runs
inside a function that returns farthing::Result<()>.
A Mesh is an indexed triangle list. Its vertices are wedges: one vertex per unique set of
attributes, so a UV seam is two vertices at the same position. GPUs use the same layout, so
you can build a mesh straight from your vertex buffers:
use farthing::Mesh;
let mut mesh = Mesh::new(positions, indices); // Vec<Vec3>, Vec<u32>
mesh.normals = Some(normals); // all optional: normals, tangents,
mesh.uvs = vec![uv0]; // any number of UV sets, colours,
mesh.material_ids = vec![0; mesh.triangle_count()]; // skin weights, morph targets
mesh.validate()?;A Scene holds meshes, instances with world transforms, skeletons and material slots. A
slot is just a name and an id. farthing never knows what a material looks like. The io
feature reads and writes glTF, GLB and OBJ:
let scene = farthing::io::load("model.glb")?; // .glb, .gltf or .obj
let (scene, materials) = farthing::io::load_gltf("model.glb")?; // plus the decoded glTF materials
let (scene, materials) = farthing::io::load_gltf_with(&bytes, |uri| files.get(uri).cloned())?; // no file system
let scene = farthing::Scene::from_mesh(mesh); // one mesh at the originsimplify reduces one mesh. The result includes the largest deviation it introduced, in
world units:
use farthing::reduce::{simplify, SimplifyOptions};
let r = simplify(&mesh, &SimplifyOptions::ratio(0.25))?;
println!("{} triangles, error {}", r.mesh.triangle_count(), r.error);There are three ways to set a target: a share of the triangles (target_ratio), a count
(target_triangles) or an error bound (max_error). With more than one target, stop decides
whether reduction ends at the first one met (StopWhen::Any) or the last (StopWhen::All).
For an object that is never larger than a known size on screen, convert pixels to an error
bound:
use farthing::reduce::{screen_size_to_max_error, SimplifyOptions};
// 300 px across on screen, allowed to be off by half a pixel.
let max_error = screen_size_to_max_error(mesh.bounds().diagonal(), 300.0, 0.5);
let opts = SimplifyOptions { max_error: Some(max_error), ..Default::default() };The defaults are what scored best in the benchmarks. These are the options you're most likely to change:
use farthing::reduce::{Quality, SeamMode, SimplifyOptions, Symmetry};
let opts = SimplifyOptions {
target_ratio: Some(0.1),
seams: SeamMode::Strict, // UV islands keep their exact outlines (may stop short of the target)
symmetry: Symmetry::Auto, // find a mirror plane and keep both halves identical
lock_border: true, // open edges stay put, so tiles and modular pieces still line up
error_normalization: 1.0, // classic QEM: protects small features, costs overall fidelity
quality: Quality::Fast, // several times faster; skips symmetry and part removal
..Default::default()
};A few more options:
vertex_importancescales the cost of collapsing each vertex.locked_verticespins vertices in place.transparent_materialslists glass and visors, so parts behind them still count as visible.
The result also maps vertices both ways:
vertex_remapgives, for each input vertex, the output vertex it collapsed into. Use it to geomorph between levels.source_vertexgives, for each output vertex, the input vertex it copies. Use it to carry your own vertex data across.
simplify_scene reduces every mesh of a scene in parallel.
lod_chain reduces each level from the one before and adds up the errors, so every level
reports how far it is from the original:
use farthing::reduce::{lod_chain, SimplifyOptions};
let levels = lod_chain(&mesh, &[0.25, 0.0625, 0.015625], &SimplifyOptions::default())?;
let (screen_height, fov_y) = (1080.0, 60f32.to_radians());
for (i, level) in levels.iter().enumerate() {
// The distance at which this level's error covers one pixel: switch to it from there out.
let switch = level.error * screen_height / (2.0 * (fov_y / 2.0).tan());
println!("LOD{i}: {} triangles, from {switch:.1} m", level.triangles);
}Errors are in the mesh's own units. For an instance drawn at a scale, multiply by the scale.
Far away, an original's UV layout wastes texture: every material keeps its own islands, and the seams between them keep the reducer from simplifying further. Drop the attributes, reduce the bare geometry, and give it one fresh atlas:
use farthing::reduce::{repair, simplify, SimplifyOptions};
use farthing::uv::{parameterize, ParamOptions};
let (flat, _) = scene.flatten(); // every instance in world space
let mut far = repair::geometry_only(&flat); // positions only: no seams to hold it back
far.material_ids.iter_mut().for_each(|m| *m = 0);
let opts = SimplifyOptions { target_triangles: Some(500), ..Default::default() };
let mut far = simplify(&far, &opts)?.mesh;
far.compute_normals(60.0); // hard edges above 60°
let uv = parameterize(&far, &ParamOptions { atlas_size: 512, padding: 2, ..Default::default() })?;
println!("{} charts, {:.0}% of the atlas used", uv.charts, uv.utilization * 100.0);parameterize returns the mesh split along chart boundaries, with the new UVs in set 0. Every
chart gets the same texel density. Set texels_per_unit to choose that density yourself.
ParamOptions::planar() is a faster unwrap for boxy shapes.
The new mesh needs textures made from the original's materials. farthing works out where each
texel comes from on the source surface, and your engine decides what that point looks like,
through the RenderHost trait:
use farthing::glam::Vec4;
use farthing::host::*;
use farthing::image::Image;
use farthing::MaterialId;
struct MyEngine { /* your materials */ }
impl RenderHost for MyEngine {
fn material_info(&self, _material: MaterialId) -> MaterialInfo {
MaterialInfo::default() // opaque, one-sided
}
fn bake_surface(&mut self, req: &SurfaceBakeRequest) -> farthing::Result<ChannelImages> {
// One optional sample per texel: the source surface's world position, normal,
// tangent, both UV sets and vertex colour at that texel, and its material.
let mut color = Image::new(req.width, req.height, Vec4::ZERO);
for (i, s) in req.samples.iter().enumerate() {
if let Some(s) = s {
color.pixels[i] = self.shade(s.material, s.uv[0], s.position);
}
}
let mut out = ChannelImages::new();
let image = ChannelImage { image: color, encoding: ChannelEncoding::Color };
out.insert(channels::BASE_COLOR.into(), image);
Ok(out)
}
fn render_views(&mut self, _req: &ViewRenderRequest) -> farthing::Result<Vec<ViewImages>> {
todo!("only impostors need this")
}
fn create_material(&mut self, _desc: &BakedMaterialDesc) -> farthing::Result<MaterialId> {
todo!("only needed when you ask farthing to make the material")
}
}Your engine shades points instead of handing over textures. That's why world-space,
triplanar and procedural materials bake correctly. GeometryOnlyHost is a host with no
materials. It is enough for reduction, proxies, occluders, bones and visibility. With a host
in hand, bake:
use farthing::bake::{bake_with_host, compute_tangents, BakeOptions};
use farthing::glam::Mat4;
use farthing::host::channels;
let mut far = uv.mesh;
compute_tangents(&mut far, 0)?; // MikkTSpace, for the baked normal map
let opts = BakeOptions {
width: 512,
height: 512,
channels: vec![channels::BASE_COLOR.into(), channels::NORMAL.into(), channels::ORM.into()],
..Default::default()
};
let baked = bake_with_host(&mut engine, &far, Mat4::IDENTITY, &scene, &opts)?;
let color = &baked.textures[channels::BASE_COLOR].image;A few things the bake handles for you:
- Normals. Return them as
ChannelEncoding::NormalWorldand farthing converts them to the new mesh's tangent space. - Quality. Each texel is supersampled (2×2 by default), and the edges are dilated into the gutters.
- Re-baking.
baked.correspondencekeeps the traced map from texels to the source surface, so you can bake more channels later without tracing again. - Textures you already have. If your materials are already flat images in the source
UVs,
bake_flattenedcopies texels and needs no host at all.
If your engine shades on a GPU queue, on another thread or across an FFI boundary, split the bake in two:
use farthing::bake::{finish_bake, prepare_bake};
let prepared = prepare_bake(&far, Mat4::IDENTITY, &scene, &opts)?;
let shaded = gpu.shade(&prepared.request).await; // ChannelImages; any time, any thread
let baked = finish_bake(prepared, shaded)?;remesh turns a whole scene into one closed, manifold mesh. It works on kitbashes, piles of
intersecting parts and buildings with interiors you'll never see, and gives a clean stand-in
for the far distance. Unwrap and bake it as above.
use farthing::voxel::{remesh, RemeshOptions};
let proxy = remesh(&scene, &RemeshOptions {
resolution: 128, // voxels along the longest side
close_gaps: 2, // seal leaks up to two voxels wide
target_triangles: Some(3000),
..Default::default()
})?;occluder builds a low-poly mesh that stays inside the original, for occlusion culling.
box_occluders returns a few boxes instead:
use farthing::voxel::{box_occluders, occluder, OccluderOptions};
let occ = occluder(&scene, &OccluderOptions { target_triangles: 300, ..Default::default() })?;
let boxes = box_occluders(&scene, 64, 8)?; // up to 8 boxes from a 64-voxel gridOnce a model is a few dozen pixels on screen, a camera-facing card showing a picture of it costs less than any mesh. Give farthing the size on screen where the impostor takes over. It plans the cameras, has your engine render them, and packs the atlas:
use farthing::impostor::{build_impostor, frame_size_for_screen, ImpostorKind};
let frame = frame_size_for_screen(96.0); // 96 px across when the impostor takes over
let kind = ImpostorKind::Octahedral { frames_per_side: 12, hemisphere: false };
let imp = build_impostor(&mut engine, &scene, &kind, frame, false)?;
// imp.atlas: colour, normal and height atlases. imp.quad: the card.
// imp.params: serialize it next to the atlas for the shader.Other kinds of impostor:
hemisphere: true, for objects never seen from below.ImpostorKind::Flipbook, a ring of views around the object.ImpostorKind::SingleView, one view from a fixed direction.build_layered_impostor, which captures a second depth layer for concave props.billboard_cloudandprogressive_surfel_cloud, for shapes one card can't hold.impostor::splats, which turns a surfel cloud into 3D Gaussians and writes the standard 3D Gaussian Splatting.ply, for foliage in any splat renderer. With footprints fromimpostor::footprint, each splat takes the shape of the patch of surface its surfel stands for: stretched along leaf strips, as opaque as the patch is covered (so coarse levels of sparse foliage stay see-through), and thicker and rougher where the normals spread. Colours come from your engine, evaluated at each surfel's source point or averaged over its patch (average_surfel_channel).
The reference shader is
shaders/octahedral_impostor.wgsl.
aggregate merges a scene into one mesh with one atlas per material class, so a room full
of props becomes one draw call. Then copy each texture into the atlas:
use farthing::aggregate::{aggregate, copy_texels, AggregateOptions};
let infos: Vec<_> = scene.materials.iter().map(|m| engine.material_info(m.id)).collect();
let merged = aggregate(&scene, &infos, &AggregateOptions { atlas_size: 2048, ..Default::default() })?;
let color = copy_texels(&merged, &flattened, channels::BASE_COLOR)?; // flattened: FlattenedTexturesbuild_cluster_dag builds the cluster hierarchy that Nanite-style renderers select from per
cluster: meshlets at the bottom, coarser clusters above, one root at the top. Every cluster
indexes the source mesh's vertex buffer, so all levels share it.
use farthing::cluster::{build_cluster_dag, ClusterOptions, LodView};
let dag = build_cluster_dag(&mesh, &ClusterOptions::default())?; // or ClusterOptions::fast()
// Per cluster: indices, bounds, normal cone, lod_error/lod_bounds, parent_error/parent_bounds.
// On the GPU: draw a cluster when its own error projects under the threshold and its parent's
// doesn't. Errors and bounds only grow towards the roots, so any camera gets a crack-free cut.
let visible = dag.select(&LodView { eye, pixels_per_radian, threshold: 1.0 });
let (vertices, local_indices) = dag.clusters[0].meshlet(); // u8 meshlet layoutCompared with a DAG built the way meshoptimizer's clusterlod.h builds one, at equal measured
on-screen error: on the 135k-triangle dragon it needs 1.7–4× fewer triangles (up to 16× in
fast mode), and it always reaches a single root. On small assets the results are mixed, and
meshoptimizer wins on the Lantern. See BENCHMARKS.
build_hlod turns everything in a scene into one stand-in for the far distance. Before
merging, it renders the scene from a ring of cameras and removes what none of them see: the
insides of crates, the floors under walls, faces pressed together. Pebbles and bolts too small to
matter at that distance are kept or dropped whole, never as shards. The rest is merged, reduced
(or voxel-remeshed into one shell), unwrapped, and baked from the visible source, so hidden
geometry never bleeds into the atlas.
use farthing::hlod::{build_hlod, HlodGeometry, HlodOptions};
let hlod = build_hlod(&mut engine, &scene, &HlodOptions {
target_triangles: Some(2000),
geometry: HlodGeometry::Reduce, // or HlodGeometry::Remesh(RemeshOptions { .. })
..Default::default() // 64 cameras, 1024² atlas, colour + normal + ORM
})?;
// hlod.mesh: world-space proxy. hlod.bake: its textures. hlod.hidden_triangles: what was culled.Set bake: None to get the unwrapped mesh and bake in your engine, and visibility: None to
keep everything. Use CameraSet::Hemisphere for things standing on the ground, so their
undersides go too.
Nested cells. build_hlod_tree partitions a whole level and builds a tree of proxies, as
world-partition HLOD layers do. Every instance goes to one cell of a loose octree (or a
quadtree over XZ) by the centre of its bounds. Instances too large for a cell go up to the first
level whose cells fit them. Each leaf cell gets a proxy of its instances, and each parent gets
a proxy of its children's proxies (or of the source, with ParentSource::Source). Cells double
in size per level, and so does the error budget. Every node gets the same triangle budget, at
most half its input, so each level is coarser than the one below. Hidden-surface removal for a
node sees everything it replaces at once.
use farthing::cluster::LodView;
use farthing::hlod::{build_hlod_tree, HlodOptions, HlodTreeOptions};
let tree = build_hlod_tree(&mut engine, &scene, &HlodTreeOptions {
cell_size: 64.0, // leaf cells; level l is 64 × 2^l
proxy: HlodOptions { target_triangles: Some(2000), ..Default::default() },
..Default::default()
})?;
// Per node: bounds, the instances it replaces, its proxy (mesh + bake + engine material),
// and an error in world units: draw it beyond node.switch_distance(pixels_per_radian, 1.0).
let cut = tree.select(&LodView { eye, pixels_per_radian, threshold: 1.0 });
// cut.nodes: proxies to draw. cut.instances: originals to draw. Each instance exactly once.A node's error uses the same convention as cluster LOD, and counts one atlas texel as error
too, so a proxy never shows while its texels are bigger than the pixel threshold. Built from
children, errors add up level by level. build_hlod_tree_with hands each node to a callback as
soon as the builder no longer needs it, so an exporter can write proxies out and free them as it
goes. The CLI's --cells writes one glTF per node and an hlod.json manifest with each node's
bounds, instances, error and switch distance.
Remove what no camera can see, such as the underside of a building or the inside of a closed box:
use farthing::visibility::{compute_visibility, cull_invisible, CameraSet, VisibilityOptions};
let vis = compute_visibility(&mut engine, &scene, &VisibilityOptions {
cameras: CameraSet::Hemisphere { count: 64, distance_factor: 2.5 },
resolution: 256,
})?;
let (culled, removed) = cull_invisible(&scene, &vis, 1); // keep one ring around what was seenThe default render_ids is a CPU ray caster that treats alpha-tested materials as opaque.
Override it in your host to let cutouts through. CameraSet::Points places cameras along a
play path instead.
A character's far levels need fewer bones and fewer influences per vertex:
use farthing::bones::*;
let opts = BoneReductionOptions { ratio: Some(0.5), ..Default::default() };
let plan = plan_bone_reduction(&skeleton, &[&mesh], None, &opts)?;
apply_bone_reduction(&mut mesh, &plan);
let (skeleton, remap) = compact_skeleton(&skeleton, &plan);
remap_joint_indices(&mut mesh, &remap);
limit_influences(&mut mesh, 2);Pass animation poses as the third argument and each bone's removal error is measured in motion, not just from the bind pose.
use farthing::io::{save_glb, ExportTextures};
use farthing::reduce::optimize::optimize_mesh;
optimize_mesh(&mut far); // vertex cache and vertex fetch order
let out = farthing::Scene::from_mesh(far);
let mut tex = ExportTextures::default();
tex.base_color.insert(0, baked.textures[channels::BASE_COLOR].image.clone());
tex.normal.insert(0, baked.textures[channels::NORMAL].image.clone());
save_glb(&out, "far.glb", &tex)?; // glb_bytes(&out, &tex) for memoryThe option structs implement serde, so pipeline settings can live in your asset database.
Work runs on rayon's thread pool, and the output is the same on any thread count.
- Speed. The default reducer is 5–9× slower than meshoptimizer on small assets and 2–3× on large ones. About 88% of the work runs in parallel. Fast mode closes most of the gap.
- Cluster DAG build speed. Faster than a serial meshoptimizer
clusterlod.h-style build from about 4 threads up, but 1.4× (fast) to 3× (default) slower per core. On Windows, link mimalloc as the global allocator: the system heap limits parallel scaling. - Worst-case distance against positions-only reducers. Ranking by area-weighted error
trades a little worst-case distance for much better mean, UV and normal fidelity. Set
error_normalization = 1.0for classic QEM when only the outline matters. - Very aggressive targets (1% and below). farthing leads on every quality average, but a few cases are still worse, such as a watch's strap lugs at 1%.
- Proxies can trail marching cubes on deep, narrow slots (the dragon: 1.39% vs 0.87% worst case, though marching cubes leaves 6% of it open). farthing's proxies are always closed and manifold.
- Opaque proxies can't reproduce transmission or refraction, as the visor above shows.
The details, and what was tried and reverted, are in docs/BENCHMARKS.md. Technique sources are in PROVENANCE.md, and ideas borrowed from other sciences are in docs/research/.
Patents on techniques in farthing's space that it deliberately stays clear of, with how it does so. Status and dates are from Google Patents as of November 2026; Google itself labels status an assumption, not a legal conclusion. This is a working list, not legal advice, and farthing has not had a freedom-to-operate review. If you know of one that belongs here, open an issue.
Active: designed around
| Patent | Owner | What it claims, in short | Status | How farthing stays clear |
|---|---|---|---|---|
| US 12,112,510 B2, Convolutional approach to fast and compact packing of 3D mesh into 2D maps | Tencent America LLC | Placing each UV chart where a correlation between the atlas occupancy map and the chart's occupancy matrix shows no overlap | Active, granted 2024-10-08, est. expiry 2043-05-11 | The packer drops charts onto a per-column skyline outline; it never correlates masks against an occupancy map. FFT/correlation placement, proposed twice in the v8 research, is off the table. |
| US 12,394,102 B2, Mesh parameterization with temporally correlated UV atlases | Tencent America LLC | Generating a frame's UV atlas by aligning its charts with reference charts from previous frames, via packing | Granted 2025-08-19 (filed 2023-03-16) | farthing parameterizes each mesh on its own; it has no per-frame atlases for animated sequences. Anything like that would need a different design. |
| US 9,978,176 B2, Simplifying small mesh components with redundant backs | Electronic Arts Inc. | Casting a ray along a surface's normal and removing that surface from a component when the ray hits other geometry within a threshold distance | Active, granted 2018-05-22, est. expiry 2036-03-31 | Part removal (v8) deletes whole connected parts or nothing, using closest-point distances. It casts no normal rays and never strips individual surfaces from a part. |
Expired: free for everyone (listed for credit, and so nobody avoids them by mistake)
| Patent | Owner | What it covered | Status |
|---|---|---|---|
| US 6,362,820 B1, Quadric metric for simplifying meshes with appearance attributes (Hoppe) | Microsoft | The attribute quadric with wedges that farthing's reducer builds on | Expired (lifetime) |
| US 8,860,723 B2, Bounded simplification of geometrical computer data | Donya Labs AB (Simplygon), now Microsoft | Collapse-based reduction that projects removed geometry onto the reduced mesh and stops at an error bound | Lapsed 2022-11-21 for unpaid fees |
| US 8,698,809 B2, Creation and rendering of hierarchical digital multimedia data | Donya Labs AB, now Microsoft | Repeatedly combining objects and simplifying each combination into a stand-in (hierarchical LOD) | Expired (fees) |
crates/farthing/ the library: data model at the root, then io/ reduce/ uv/ bake/
aggregate/ voxel/ visibility/ impostor/ bones/, reference WGSL in shaders/
crates/farthing-cli/ the `farthing` binary
demo-web/ WebAssembly browser demo (separate workspace)
engine-poc/ wgpu engine implementing RenderHost (separate workspace)
bench-compare/ head-to-head vs meshoptimizer, xatlas, baby_shark (separate workspace)
docs/ BENCHMARKS.md, raw benchmark data, figures
License: MIT-0 (MIT No Attribution).