Skip to content

Commit

Permalink
weld: Expose 'toleranceNormal' option. (#1046)
Browse files Browse the repository at this point in the history
* feat(weld): Expose 'toleranceNormal' option.

* Fix tests
  • Loading branch information
donmccurdy authored Jul 29, 2023
1 parent 315dd73 commit 9255269
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 12 deletions.
17 changes: 15 additions & 2 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,12 @@ commands or using the scripting API.
if (opts.join) transforms.push(dequantize(), join());

if (opts.weld) {
transforms.push(weld({ tolerance: opts.simplify ? opts.simplifyError / 2 : WELD_DEFAULTS.tolerance }));
transforms.push(
weld({
tolerance: opts.simplify ? opts.simplifyError / 2 : WELD_DEFAULTS.tolerance,
toleranceNormal: opts.simplify ? 0.5 : WELD_DEFAULTS.toleranceNormal,
})
);
}

if (opts.simplify) {
Expand Down Expand Up @@ -821,14 +826,22 @@ bounding box (AABB). For example, --tolerance=0.01 welds vertices within +/-1%
of the AABB's longest dimension. Other vertex attributes are also compared
during welding, with attribute-specific thresholds. For --tolerance=0, geometry
is indexed in place, without merging.
To preserve visual appearance consistently, use low --tolerance-normal thresholds
around 0.1 (±3º). To pre-processing a scene before simplification or LOD creation,
use higher thresholds around 0.5 (±30º).
`.trim()
)
.argument('<input>', INPUT_DESC)
.argument('<output>', OUTPUT_DESC)
.option('--tolerance', 'Tolerance for vertex welding', {
.option('--tolerance', 'Tolerance for vertex positions, as a fraction of primitive AABB', {
validator: program.NUMBER,
default: WELD_DEFAULTS.tolerance,
})
.option('--tolerance-normal', 'Tolerance for vertex normals, in radians', {
validator: program.NUMBER,
default: WELD_DEFAULTS.toleranceNormal,
})
.action(({ args, options, logger }) =>
Session.create(io, logger, args.input, args.output).transform(weld(options as unknown as WeldOptions))
);
Expand Down
35 changes: 25 additions & 10 deletions packages/functions/src/weld.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,17 @@ const Tolerance = {
DEFAULT: 0.0001,
TEXCOORD: 0.0001, // [0, 1]
COLOR: 0.01, // [0, 1]
NORMAL: 0.05, // [-1, 1]
NORMAL: 0.05, // [-1, 1], ±3º
JOINTS: 0.0, // [0, ∞]
WEIGHTS: 0.01, // [0, ∞]
};

/** Options for the {@link weld} function. */
export interface WeldOptions {
/** Tolerance, as a fraction of primitive AABB, used when merging similar vertices. */
/** Tolerance for vertex positions, as a fraction of primitive AABB. */
tolerance?: number;
/** Tolerance for vertex normals, in radians. */
toleranceNormal?: number;
/** Whether to overwrite existing indices. */
overwrite?: boolean;
/** Enables a more thorough, but slower, search for vertices to weld. */
Expand All @@ -65,6 +67,7 @@ export interface WeldOptions {

export const WELD_DEFAULTS: Required<WeldOptions> = {
tolerance: Tolerance.DEFAULT,
toleranceNormal: Tolerance.NORMAL,
overwrite: true,
exhaustive: false, // donmccurdy/glTF-Transform#886
};
Expand All @@ -78,16 +81,20 @@ export const WELD_DEFAULTS: Required<WeldOptions> = {
* welding based on distance between the vertices as a fraction of the primitive's
* bounding box (AABB). For example, tolerance=0.01 welds vertices within +/-1%
* of the AABB's longest dimension. Other vertex attributes are also compared
* during welding, with attribute-specific thresholds. For --tolerance=0, geometry
* during welding, with attribute-specific thresholds. For `tolerance=0`, geometry
* is indexed in place, without merging.
*
* To preserve visual appearance consistently, use low `toleranceNormal` thresholds
* around 0.1 (±3º). To pre-processing a scene before simplification or LOD creation,
* use higher thresholds around 0.5 (±30º).
*
* Example:
*
* ```javascript
* import { weld } from '@gltf-transform/functions';
*
* await document.transform(
* weld({ tolerance: 0.001 })
* weld({ tolerance: 0.001, toleranceNormal: 0.5 })
* );
* ```
*
Expand All @@ -96,10 +103,19 @@ export const WELD_DEFAULTS: Required<WeldOptions> = {
export function weld(_options: WeldOptions = WELD_DEFAULTS): Transform {
const options = { ...WELD_DEFAULTS, ..._options } as Required<WeldOptions>;

if (options.tolerance > 0.1 || options.tolerance < 0) {
if (options.tolerance < 0 || options.tolerance > 0.1) {
throw new Error(`${NAME}: Requires 0 ≤ tolerance ≤ 0.1`);
}

if (options.toleranceNormal < 0 || options.toleranceNormal > Math.PI / 2) {
throw new Error(`${NAME}: Requires 0 ≤ toleranceNormal ≤ ${(Math.PI / 2).toFixed(2)}`);
}

if (options.tolerance > 0) {
options.tolerance = Math.max(options.tolerance, Number.EPSILON);
options.toleranceNormal = Math.max(options.toleranceNormal, Number.EPSILON);
}

return createTransform(NAME, async (doc: Document): Promise<void> => {
const logger = doc.getLogger();

Expand Down Expand Up @@ -181,11 +197,10 @@ function _weldPrimitive(doc: Document, prim: Primitive, options: Required<WeldOp

// (1) Compute per-attribute tolerance and spatial grid for vertices.

const baseTolerance = Math.max(options.tolerance, Number.EPSILON);
const attributeTolerance: Record<string, number> = {};
for (const semantic of prim.listSemantics()) {
const attribute = prim.getAttribute(semantic)!;
attributeTolerance[semantic] = getAttributeTolerance(semantic, attribute, baseTolerance);
attributeTolerance[semantic] = getAttributeTolerance(semantic, attribute, options);
}

logger.debug(`${NAME}: Tolerance thresholds: ${formatKV(attributeTolerance)}`);
Expand Down Expand Up @@ -324,10 +339,10 @@ const _a = [] as number[];
const _b = [] as number[];

/** Computes a per-attribute tolerance, based on domain and usage of the attribute. */
function getAttributeTolerance(semantic: string, attribute: Accessor, tolerance: number): number {
function getAttributeTolerance(semantic: string, attribute: Accessor, options: Required<WeldOptions>): number {
// Attributes like NORMAL and COLOR_# do not vary in range like POSITION,
// so do not apply the given tolerance factor to these attributes.
if (semantic === 'NORMAL' || semantic === 'TANGENT') return Tolerance.NORMAL;
if (semantic === 'NORMAL' || semantic === 'TANGENT') return options.toleranceNormal;
if (semantic.startsWith('COLOR_')) return Tolerance.COLOR;
if (semantic.startsWith('TEXCOORD_')) return Tolerance.TEXCOORD;
if (semantic.startsWith('JOINTS_')) return Tolerance.JOINTS;
Expand All @@ -338,7 +353,7 @@ function getAttributeTolerance(semantic: string, attribute: Accessor, tolerance:
attribute.getMaxNormalized(_b);
const diff = _b.map((bi, i) => bi - _a[i]);
const range = Math.max(...diff);
return tolerance * range;
return options.tolerance * range;
}

/** Compares two vertex attributes against a tolerance threshold. */
Expand Down

0 comments on commit 9255269

Please sign in to comment.