start jsdoc with geo/base

This commit is contained in:
Stewart Allen 2025-12-15 22:53:36 -05:00
commit 6f6cf1929d

View file

@ -5,10 +5,22 @@ import earcut from '../ext/earcut.js';
const round_decimal_precision = 5; const round_decimal_precision = 5;
/**
* Get current timestamp in milliseconds
* @returns {number} Current time from Date.now()
*/
function time() { function time() {
return Date.now() return Date.now()
} }
/**
* Linear interpolation between two values with maximum increment per step
* @param {number} from - Starting value
* @param {number} to - Target value
* @param {number} maxInc - Maximum increment per step
* @param {boolean} [incFrom=false] - Include starting value in output array
* @returns {number[]} Array of interpolated values from 'from' to 'to'
*/
function lerp(from, to, maxInc, incFrom) { function lerp(from, to, maxInc, incFrom) {
let dir = Math.sign(to - from); let dir = Math.sign(to - from);
let delta = Math.abs(to - from); let delta = Math.abs(to - from);
@ -27,7 +39,12 @@ function lerp(from, to, maxInc, incFrom) {
return out; return out;
} }
/** track an array of promises as they all complete */ /**
* Track an array of promises as they all complete
* @param {Promise[]} promises - Array of promises to wait for
* @param {Function} [tracker] - Optional callback(index, total, data) called as each promise resolves
* @returns {Promise<void>} Resolves when all promises complete
*/
async function pwait(promises, tracker) { async function pwait(promises, tracker) {
let count = 0; let count = 0;
if (tracker) if (tracker)
@ -39,13 +56,23 @@ async function pwait(promises, tracker) {
await Promise.all(promises); await Promise.all(promises);
} }
/** return a promise that resolves after a given time */ /**
* Return a promise that resolves after a given time
* @param {number} time - Delay in milliseconds
* @returns {Promise<void>} Promise that resolves after the specified delay
*/
function ptimer(time) { function ptimer(time) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
setTimeout(resolve, time); setTimeout(resolve, time);
}); });
} }
/**
* Return a number or default value if undefined
* @param {number} num - Value to check
* @param {number} def - Default value to return if num is undefined
* @returns {number} The number or default value
*/
function numOrDefault(num, def) { function numOrDefault(num, def) {
return num !== undefined ? num : def; return num !== undefined ? num : def;
} }
@ -70,14 +97,35 @@ function doCombinations(a1, a2, arg, fn) {
return arg; return arg;
} }
/**
* Test if three points form a clockwise turn
* @param {Point} p1 - First point
* @param {Point} p2 - Second point
* @param {Point} p3 - Third point
* @returns {boolean} True if points progress in clockwise direction
*/
function isClockwise(p1, p2, p3) { function isClockwise(p1, p2, p3) {
return area2(p1, p2, p3) > 0; return area2(p1, p2, p3) > 0;
} }
/**
* Test if three points form a counter-clockwise turn
* @param {Point} p1 - First point
* @param {Point} p2 - Second point
* @param {Point} p3 - Third point
* @returns {boolean} True if points progress in counter-clockwise direction
*/
function isCounterClockwise(p1, p2, p3) { function isCounterClockwise(p1, p2, p3) {
return area2(p1, p2, p3) < 0; return area2(p1, p2, p3) < 0;
} }
/**
* Compute partial area contribution for two points (used in area2 calculation)
* @param {Point} p1 - First point
* @param {Point} p2 - Second point
* @returns {number} Partial area value
* @private
*/
function pac(p1, p2) { function pac(p1, p2) {
return (p2.x - p1.x) * (p2.y + p1.y); return (p2.x - p1.x) * (p2.y + p1.y);
} }
@ -90,22 +138,45 @@ function area2(p1, p2, p3) {
return pac(p1, p2) + pac(p2, p3) + pac(p3, p1); return pac(p1, p2) + pac(p2, p3) + pac(p3, p1);
} }
/**
* Test if two values are close to each other within a distance threshold
* @param {number} v1 - First value
* @param {number} v2 - Second value
* @param {number} [dist] - Distance threshold (defaults to precision_merge config)
* @returns {boolean} True if values are within threshold distance
*/
function isCloseTo(v1, v2, dist) { function isCloseTo(v1, v2, dist) {
return Math.abs(v1 - v2) <= (dist ?? base.config.precision_merge); return Math.abs(v1 - v2) <= (dist ?? base.config.precision_merge);
} }
/**
* Test if a value is within a range, allowing for close-to precision at boundaries
* @param {number} val - Value to test
* @param {number} min - Minimum value
* @param {number} max - Maximum value
* @param {number} [precisiom] - Precision threshold for boundary comparison
* @returns {boolean} True if value is in range or close to boundaries
*/
function inCloseRange(val, min, max, precisiom) { function inCloseRange(val, min, max, precisiom) {
return (isCloseTo(val, min, precisiom) || val >= min) && (isCloseTo(val, max, precisiom) || val <= max); return (isCloseTo(val, min, precisiom) || val >= min) && (isCloseTo(val, max, precisiom) || val <= max);
} }
/** /**
* return square of value * Return square of value
* @param {number} v - Value to square
* @returns {number} v²
*/ */
function sqr(v) { function sqr(v) {
return v * v return v * v
} }
// radians rotatition around origin /**
* Rotate a 2D point around the origin by radians
* @param {number} x - X coordinate
* @param {number} y - Y coordinate
* @param {number} radians - Rotation angle in radians
* @returns {number[]} Rotated [x, y] coordinates
*/
function rotate(x,y,radians) { function rotate(x,y,radians) {
return [ return [
x * Math.cos(radians) - y * Math.sin(radians), x * Math.cos(radians) - y * Math.sin(radians),
@ -116,45 +187,84 @@ function rotate(x,y,radians) {
const deg2rad = (Math.PI / 180); const deg2rad = (Math.PI / 180);
const rad2deg = (180 / Math.PI); const rad2deg = (180 / Math.PI);
/**
* Convert degrees to radians
* @param {number} degrees - Angle in degrees
* @returns {number} Angle in radians
*/
function toRadians(degrees) { function toRadians(degrees) {
return degrees * deg2rad; return degrees * deg2rad;
} }
/**
* Convert radians to degrees
* @param {number} radians - Angle in radians
* @returns {number} Angle in degrees
*/
function toDegrees(radians) { function toDegrees(radians) {
return radians * rad2deg; return radians * rad2deg;
} }
/** /**
* return distance between two points * Return 2D distance between two points
* @param {Point} p1 - First point
* @param {Point} p2 - Second point
* @returns {number} Euclidean distance
*/ */
function dist2D(p1, p2) { function dist2D(p1, p2) {
return Math.sqrt(distSq(p1, p2)); return Math.sqrt(distSq(p1, p2));
} }
/** /**
* return distance squared between two points * Return squared distance between two points (faster than dist2D, avoids sqrt)
* @param {Point} p1 - First point
* @param {Point} p2 - Second point
* @returns {number} Distance squared
*/ */
function distSq(p1, p2) { function distSq(p1, p2) {
return sqr(p2.x - p1.x) + sqr(p2.y - p1.y) return sqr(p2.x - p1.x) + sqr(p2.y - p1.y)
} }
/** /**
* return distance squared between two points * Return squared distance between two coordinate pairs (faster variant)
* enables faster Point.nearPolygon() * @param {number} x1 - First X coordinate
* @param {number} y1 - First Y coordinate
* @param {number} x2 - Second X coordinate
* @param {number} y2 - Second Y coordinate
* @returns {number} Distance squared
*/ */
function distSqv2(x1, y1, x2, y2) { function distSqv2(x1, y1, x2, y2) {
return sqr(x2 - x1) + sqr(y2 - y1) return sqr(x2 - x1) + sqr(y2 - y1)
} }
/**
* Calculate adjusted offset accounting for precision tolerance
* @param {number} offset - Offset value
* @param {number} precision - Precision threshold
* @returns {number} Adjusted offset
*/
function offsetPrecision(offset, precision) { function offsetPrecision(offset, precision) {
return Math.abs(offset) - precision; return Math.abs(offset) - precision;
} }
/**
* Test if a value is within a numeric range
* @param {*} value - Value to test (will be parsed as float)
* @param {number} min - Minimum value (inclusive)
* @param {number} max - Maximum value (inclusive)
* @returns {boolean} True if value is in range
*/
function inRange(value, min, max) { function inRange(value, min, max) {
let val = parseFloat(value); let val = parseFloat(value);
return val >= min && val <= max; return val >= min && val <= max;
} }
/**
* Round a number or all numeric properties of an object to specified decimal places
* @param {number|Object} v - Value or object to round
* @param {number} [zeros] - Number of decimal places (defaults to round_decimal_precision=5)
* @returns {number|Object} Rounded value or object with rounded properties
*/
function round(v, zeros) { function round(v, zeros) {
if (typeof v === 'object') { if (typeof v === 'object') {
for (let [key,val] of Object.entries(v)) { for (let [key,val] of Object.entries(v)) {
@ -170,6 +280,13 @@ function round(v, zeros) {
return Math.round(v * pow) / pow; return Math.round(v * pow) / pow;
} }
/**
* Clamp a value between low and high bounds
* @param {number} val - Value to clamp
* @param {number} low - Minimum bound
* @param {number} hi - Maximum bound
* @returns {number} Value clamped to [low, hi] range
*/
function clamp(val, low, hi) { function clamp(val, low, hi) {
return Math.max(low, Math.min(hi, val)); return Math.max(low, Math.min(hi, val));
} }
@ -412,9 +529,14 @@ function thetaDiff(n1, n2, clockwise) {
return diff; return diff;
} }
// order array of elements using next closest element comparator /**
// used for things like next-closest point walks (fdm thin fill) * Order array elements by proximity using next-closest element selection
// in future, replace poly2poly and similar with this * Used for optimized path planning (e.g., FDM thin fill, traveling salesman-style)
* @param {Array} array - Array of elements to order
* @param {Function} fn - Distance function fn(from, el) => number
* @param {*} [from] - Starting element (if not provided, uses first element)
* @returns {Array} Reordered array with elements sorted by proximity
*/
function orderClosest(array, fn, from) { function orderClosest(array, fn, from) {
if (!array.length) { if (!array.length) {
return array; return array;
@ -445,10 +567,16 @@ function orderClosest(array, fn, from) {
return out; return out;
} }
// wrapper for earcut that handles higher order dimensions and finds the /**
// two with the greatest delta to pass to the earcut algorith, then returns * Wrapper for earcut triangulation that handles higher-dimensional data
// an unwrapped array in the original dimensions * Automatically selects the two dimensions with greatest delta for triangulation,
// at present, only used by load.obj.parse() * then maps results back to original dimensionality
* @param {number[]} array - Flat array of coordinates (length = numPoints * dims)
* @param {number[]} [holes] - Hole indices for polygon with holes
* @param {number} [dims=2] - Number of dimensions per point (2 or 3)
* @param {boolean} [pos=false] - If true, return position indices instead of full coordinates
* @returns {number[]} Triangulated vertex array or indices
*/
function triangulate(array, holes, dims, pos) { function triangulate(array, holes, dims, pos) {
let narray; let narray;
let info; let info;
@ -507,10 +635,20 @@ function triangulate(array, holes, dims, pos) {
return oa; return oa;
} }
/**
* Recursively flatten a nested array
* @param {Array} arr - Array to flatten (may contain nested arrays)
* @returns {Array} Flattened one-dimensional array
*/
function flatten(arr) { function flatten(arr) {
return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), []) return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), [])
} }
/**
* Format a number with thousand separators (commas)
* @param {number|string} v - Number to format
* @returns {string} Formatted string with commas (e.g., "1,234.56")
*/
function comma(v) { function comma(v) {
if (!v) return v; if (!v) return v;
let [lt, rt] = v.toString().split('.'); let [lt, rt] = v.toString().split('.');