start jsdoc with geo/base
This commit is contained in:
parent
fb74c95bfe
commit
6f6cf1929d
1 changed files with 153 additions and 15 deletions
168
src/geo/base.js
168
src/geo/base.js
|
|
@ -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('.');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue