diff --git a/packages/geom-splines/.npmignore b/packages/geom-splines/.npmignore new file mode 100644 index 0000000000..74ea62d1fa --- /dev/null +++ b/packages/geom-splines/.npmignore @@ -0,0 +1,12 @@ +.meta +.nyc_output +*.html +*.tgz +build +coverage +dev +doc +export +src* +test +tsconfig.json diff --git a/packages/geom-splines/LICENSE b/packages/geom-splines/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/packages/geom-splines/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/geom-splines/README.md b/packages/geom-splines/README.md new file mode 100644 index 0000000000..5d52f3cb73 --- /dev/null +++ b/packages/geom-splines/README.md @@ -0,0 +1,40 @@ +# @thi.ng/geom-splines + +[![npm (scoped)](https://img.shields.io/npm/v/@thi.ng/geom-splines.svg)](https://www.npmjs.com/package/@thi.ng/geom-splines) +![npm downloads](https://img.shields.io/npm/dm/@thi.ng/geom-splines.svg) +[![Twitter Follow](https://img.shields.io/twitter/follow/thing_umbrella.svg?style=flat-square&label=twitter)](https://twitter.com/thing_umbrella) + +This project is part of the +[@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo. + + + + + +## About + +TODO... + +## Installation + +```bash +yarn add @thi.ng/geom-splines +``` + +## Dependencies + +- TODO... + +## Usage examples + +```ts +import * as gs from "@thi.ng/geom-splines"; +``` + +## Authors + +- Karsten Schmidt + +## License + +© 2018 Karsten Schmidt // Apache Software License 2.0 diff --git a/packages/geom-splines/package.json b/packages/geom-splines/package.json new file mode 100644 index 0000000000..13c1ff1bd6 --- /dev/null +++ b/packages/geom-splines/package.json @@ -0,0 +1,65 @@ +{ + "name": "@thi.ng/geom-splines", + "version": "0.0.1", + "description": "nD cubic & quadratic curve analysis, conversion, interpolation, splitting", + "module": "./index.js", + "main": "./lib/index.js", + "umd:main": "./lib/index.umd.js", + "typings": "./index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/thi-ng/umbrella.git" + }, + "homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/geom-splines", + "author": "Karsten Schmidt ", + "license": "Apache-2.0", + "scripts": { + "build": "yarn clean && yarn build:es6 && yarn build:bundle", + "build:es6": "tsc --declaration", + "build:bundle": "../../scripts/bundle-module geomSplines checks geom-arc geom-resample math vectors", + "test": "rimraf build && tsc -p test/tsconfig.json && nyc mocha build/test/*.js", + "clean": "rimraf *.js *.d.ts .nyc_output build coverage doc lib", + "cover": "yarn test && nyc report --reporter=lcov", + "doc": "node_modules/.bin/typedoc --mode modules --out doc src", + "pub": "yarn build && yarn publish --access public" + }, + "devDependencies": { + "@types/mocha": "^5.2.5", + "@types/node": "^10.12.15", + "mocha": "^5.2.0", + "nyc": "^13.1.0", + "typedoc": "^0.14.0", + "typescript": "^3.2.2" + }, + "dependencies": { + "@thi.ng/checks": "^2.0.1", + "@thi.ng/geom-arc": "^0.0.1", + "@thi.ng/geom-resample": "^0.0.1", + "@thi.ng/math": "^1.0.1", + "@thi.ng/vectors": "^2.1.0" + }, + "keywords": [ + "2D", + "3D", + "nD", + "arc", + "bezier", + "bounding box", + "closest point", + "conversion", + "cubic", + "curve", + "ES6", + "geometry", + "interpolation", + "quadratic", + "resampling", + "spline", + "splitting", + "typescript" + ], + "publishConfig": { + "access": "public" + }, + "sideEffects": false +} \ No newline at end of file diff --git a/packages/geom-splines/src/cubic-arc.ts b/packages/geom-splines/src/cubic-arc.ts new file mode 100644 index 0000000000..2a2624204c --- /dev/null +++ b/packages/geom-splines/src/cubic-arc.ts @@ -0,0 +1,70 @@ +import { pointAtTheta } from "@thi.ng/geom-arc"; +import { + EPS, + HALF_PI, + roundEps, + sincos +} from "@thi.ng/math"; +import { magSq2, ReadonlyVec, Vec } from "@thi.ng/vectors"; +import { cubicFromLine } from "./cubic-line"; + +/** + * Converts elliptic arc into a 1-4 cubic curve segments, depending on + * arc's angle range. + * + * Partially based on: + * https://github.com/chromium/chromium/blob/master/third_party/blink/renderer/core/svg/svg_path_parser.cc#L253 + * + * @param pos + * @param r + * @param axis + * @param start + * @param end + */ +export const cubicFromArc = ( + pos: ReadonlyVec, + r: ReadonlyVec, + axis: number, + start: number, + end: number, +) => { + + const p = pointAtTheta(pos, r, axis, start); + const q = pointAtTheta(pos, r, axis, end); + const [rx, ry] = r; + const [s, c] = sincos(axis); + const dx = c * (p[0] - q[0]) / 2 + s * (p[1] - q[1]) / 2; + const dy = -s * (p[0] - q[0]) / 2 + c * (p[1] - q[1]) / 2; + if ((dx === 0 && dy === 0) || magSq2(r) < EPS) { + return [cubicFromLine(p, q)]; + } + + const mapP = (x: number, y: number) => { + x *= rx; + y *= ry; + return [ + c * x - s * y + pos[0], + s * x + c * y + pos[1] + ]; + }; + + const res: Vec[][] = []; + const delta = end - start; + const n = Math.max(roundEps(Math.abs(delta) / HALF_PI, 1e-3), 1); + const d = delta / n; + const t = 8 / 6 * Math.tan(0.25 * d); + if (!isFinite(t)) { + return [cubicFromLine(p, q)]; + } + for (let i = n, theta = start; i > 0; i-- , theta += d) { + const [s1, c1] = sincos(theta); + const [s2, c2] = sincos(theta + d); + res.push([ + mapP(c1, s1), + mapP(c1 - s1 * t, s1 + c1 * t), + mapP(c2 + s2 * t, s2 - c2 * t), + mapP(c2, s2), + ]); + } + return res; +}; diff --git a/packages/geom-splines/src/cubic-bounds.ts b/packages/geom-splines/src/cubic-bounds.ts new file mode 100644 index 0000000000..20435dc54a --- /dev/null +++ b/packages/geom-splines/src/cubic-bounds.ts @@ -0,0 +1,44 @@ +import { mixCubic } from "@thi.ng/math"; +import { ReadonlyVec, VecPair, Vec } from "@thi.ng/vectors"; + +const axisBounds = + (min: Vec, max: Vec, i: number, pa: number, pb: number, pc: number, pd: number) => { + let a = 3 * pd - 9 * pc + 9 * pb - 3 * pa; + let b = 6 * pa - 12 * pb + 6 * pc; + let c = 3 * pb - 3 * pa; + let disc = b * b - 4 * a * c; + + let l = pa; + let h = pa; + pd < l && (l = pd); + pd > h && (h = pd); + + if (disc >= 0) { + disc = Math.sqrt(disc); + a *= 2; + + const bounds = (t: number) => { + if (t > 0 && t < 1) { + const x = mixCubic(pa, pb, pc, pd, t); + x < l && (l = x); + x > h && (h = x); + } + }; + + bounds((-b + disc) / a); + bounds((-b - disc) / a); + } + + min[i] = l; + max[i] = h; + }; + +export const cubicBounds = + (a: ReadonlyVec, b: ReadonlyVec, c: ReadonlyVec, d: ReadonlyVec): VecPair => { + const min = []; + const max = []; + for (let i = a.length; --i >= 0;) { + axisBounds(min, max, i, a[i], b[i], c[i], d[i]); + } + return [min, max]; + }; diff --git a/packages/geom-splines/src/cubic-closest-point.ts b/packages/geom-splines/src/cubic-closest-point.ts new file mode 100644 index 0000000000..51d0d0b606 --- /dev/null +++ b/packages/geom-splines/src/cubic-closest-point.ts @@ -0,0 +1,39 @@ +import { minError } from "@thi.ng/math"; +import { + distSq, + mixCubic, + ReadonlyVec, + Vec +} from "@thi.ng/vectors"; + +/** + * Performs recursive search for closest point to `p` on cubic curve + * defined by control points `a`,`b`,`c`,`d`. The `res` and `recur` + * params are used to control the recursion behavior. If `eps` is given, + * the search is terminated as soon as a point with a shorter *squared* + * distance than `eps` is found. + * + * @see thi.ng/math/minError + * + * @param p + * @param a + * @param b + * @param c + * @param d + * @param res + * @param iter + */ +export const closestPointCubic = ( + p: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + d: ReadonlyVec, + out: Vec = [], + res?: number, + iter?: number, + eps?: number +) => { + const fn = (t: number) => mixCubic(out, a, b, c, d, t); + return fn(minError(fn, distSq, p, res, iter, 0, 1, eps)); +}; diff --git a/packages/geom-splines/src/cubic-line.ts b/packages/geom-splines/src/cubic-line.ts new file mode 100644 index 0000000000..f827e8caf0 --- /dev/null +++ b/packages/geom-splines/src/cubic-line.ts @@ -0,0 +1,18 @@ +import { mixN, set, Vec } from "@thi.ng/vectors"; + +/** + * Converts line segment `a` -> `b` into a cubic curve, which when + * sampled yields uniformly spaced points. The inner control points are + * located at 1/3 and 2/3 respectively. + * + * @param a + * @param b + */ +export const cubicFromLine = + (a: Vec, b: Vec) => + [ + set([], a), + mixN([], a, b, 1 / 3), + mixN([], b, a, 1 / 3), + set([], b) + ]; diff --git a/packages/geom-splines/src/cubic-quadratic.ts b/packages/geom-splines/src/cubic-quadratic.ts new file mode 100644 index 0000000000..62a085e5f0 --- /dev/null +++ b/packages/geom-splines/src/cubic-quadratic.ts @@ -0,0 +1,10 @@ +import { mixN, set, Vec } from "@thi.ng/vectors"; + +export const cubicFromQuadratic = + (a: Vec, b: Vec, c: Vec) => + [ + set([], a), + mixN([], a, b, 2 / 3), + mixN([], c, b, 2 / 3), + set([], c) + ]; diff --git a/packages/geom-splines/src/cubic-sample.ts b/packages/geom-splines/src/cubic-sample.ts new file mode 100644 index 0000000000..76c8989d48 --- /dev/null +++ b/packages/geom-splines/src/cubic-sample.ts @@ -0,0 +1,33 @@ +import { isNumber, isPlainObject } from "@thi.ng/checks"; +import { DEFAULT_SAMPLES, Sampler, SamplingOpts } from "@thi.ng/geom-resample"; +import { + mixCubic, + ReadonlyVec, + set, + Vec +} from "@thi.ng/vectors"; + +export const sampleCubic = + (pts: ReadonlyVec[], opts?: number | Partial) => { + if (isPlainObject(opts) && (opts).dist !== undefined) { + return new Sampler(sampleCubic(pts, (opts).num || DEFAULT_SAMPLES)) + .sampleUniform((opts).dist, (opts).last !== false); + } + opts = isNumber(opts) ? + { + num: opts, + last: true + } : + { + num: DEFAULT_SAMPLES, + ...opts + }; + const res: Vec[] = []; + const [a, b, c, d] = pts; + const delta = 1 / opts.num; + for (let t = 0; t < opts.num; t++) { + res.push(mixCubic([], a, b, c, d, t * delta)); + } + opts.last && res.push(set([], d)); + return res; + }; diff --git a/packages/geom-splines/src/cubic-split.ts b/packages/geom-splines/src/cubic-split.ts new file mode 100644 index 0000000000..2a3f1bb1fb --- /dev/null +++ b/packages/geom-splines/src/cubic-split.ts @@ -0,0 +1,48 @@ +import { minError } from "@thi.ng/math"; +import { + distSq, + mixCubic, + mixN, + ReadonlyVec, + set +} from "@thi.ng/vectors"; + +export const cubicSplitAt = + (a: ReadonlyVec, b: ReadonlyVec, c: ReadonlyVec, d: ReadonlyVec, t: number) => { + if (t <= 0 || t >= 1) { + const p = t <= 0 ? a : d; + const c1 = [set([], p), set([], p), set([], p), set([], p)]; + const c2 = [set([], a), set([], b), set([], c), set([], d)]; + return t <= 0 ? [c1, c2] : [c2, c1]; + } + const ab = mixN([], a, b, t); + const bc = mixN([], b, c, t); + const cd = mixN([], c, d, t); + const abc = mixN([], ab, bc, t); + const bcd = mixN([], bc, cd, t); + const p = mixN([], abc, bcd, t); + return [ + [set([], a), ab, abc, set([], p)], + [p, bcd, cd, set([], d)] + ]; + }; + +export const splitCubicNearPoint = ( + p: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + d: ReadonlyVec, + res?: number, + iter?: number +) => + cubicSplitAt( + a, b, c, d, + minError( + (t: number) => mixCubic([], a, b, c, d, t), + distSq, + p, + res, + iter + ) + ); diff --git a/packages/geom-splines/src/index.ts b/packages/geom-splines/src/index.ts new file mode 100644 index 0000000000..949ed0f1dd --- /dev/null +++ b/packages/geom-splines/src/index.ts @@ -0,0 +1,15 @@ +export * from "./cubic-arc"; +export * from "./cubic-bounds"; +export * from "./cubic-closest-point"; +export * from "./cubic-line"; +export * from "./cubic-quadratic"; +export * from "./cubic-sample"; +export * from "./cubic-split"; + +export * from "./quadratic-bounds"; +export * from "./quadratic-closest-point"; +export * from "./quadratic-line"; +export * from "./quadratic-sample"; +export * from "./quadratic-split"; + +export * from "./point-at"; diff --git a/packages/geom-splines/src/point-at.ts b/packages/geom-splines/src/point-at.ts new file mode 100644 index 0000000000..f78a3c8c69 --- /dev/null +++ b/packages/geom-splines/src/point-at.ts @@ -0,0 +1,11 @@ +import { mixCubic, mixQuadratic } from "@thi.ng/vectors"; + +/** + * Same as thi.ng/vectors/mixCubic + */ +export const cubicPointAt = mixCubic; + +/** + * Same as thi.ng/vectors/mixQuadratic + */ +export const quadraticPointAt = mixQuadratic; diff --git a/packages/geom-splines/src/quadratic-bounds.ts b/packages/geom-splines/src/quadratic-bounds.ts new file mode 100644 index 0000000000..66186e64ed --- /dev/null +++ b/packages/geom-splines/src/quadratic-bounds.ts @@ -0,0 +1,36 @@ +import { clamp01, inRange } from "@thi.ng/math"; +import { + max, + min, + ReadonlyVec, + VecPair +} from "@thi.ng/vectors"; + +const solveQuadratic = + (a: number, b: number, c: number) => { + const t = clamp01((a - b) / (a - 2.0 * b + c)); + const s = 1 - t; + return s * s * a + 2.0 * s * t * b + t * t * c; + }; + +const inBounds = + (p: ReadonlyVec, min: ReadonlyVec, max: ReadonlyVec) => { + for (let i = p.length; --i >= 0;) { + if (!inRange(p[i], min[i], max[i])) return false; + } + }; + +export const quadraticBounds = + (a: ReadonlyVec, b: ReadonlyVec, c: ReadonlyVec): VecPair => { + const mi = min([], a, c); + const ma = max([], a, c); + if (!inBounds(b, mi, ma)) { + const q = []; + for (let i = a.length; --i >= 0;) { + q[i] = solveQuadratic(a[i], b[i], c[i]); + } + min(null, mi, q); + max(null, ma, q); + } + return [mi, ma]; + }; diff --git a/packages/geom-splines/src/quadratic-closest-point.ts b/packages/geom-splines/src/quadratic-closest-point.ts new file mode 100644 index 0000000000..e033b43ae1 --- /dev/null +++ b/packages/geom-splines/src/quadratic-closest-point.ts @@ -0,0 +1,38 @@ +import { minError } from "@thi.ng/math"; +import { + distSq, + mixQuadratic, + ReadonlyVec, + Vec +} from "@thi.ng/vectors"; + +/** + * Performs recursive search for closest point to `p` on quadratic curve + * defined by control points `a`,`b`,`c`. The `res` and `recur` params + * are used to control the recursion behavior. If `eps` is given, the + * search is terminated as soon as a point with a shorter *squared* + * distance than `eps` is found. + * + * @see thi.ng/math/minError + * + * @param p + * @param a + * @param b + * @param c + * @param res + * @param iter + * @param eps + */ +export const closestPointQuadratic = ( + p: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + out: Vec = [], + res?: number, + iter?: number, + eps?: number +) => { + const fn = (t: number) => mixQuadratic(out, a, b, c, t); + return fn(minError(fn, distSq, p, res, iter, 0, 1, eps)); +}; diff --git a/packages/geom-splines/src/quadratic-line.ts b/packages/geom-splines/src/quadratic-line.ts new file mode 100644 index 0000000000..ab5c66f461 --- /dev/null +++ b/packages/geom-splines/src/quadratic-line.ts @@ -0,0 +1,9 @@ +import { addmN, ReadonlyVec, set } from "@thi.ng/vectors"; + +export const quadraticFromLine = + (a: ReadonlyVec, b: ReadonlyVec) => + [ + set([], a), + addmN([], a, b, 0.5), + set([], b) + ]; diff --git a/packages/geom-splines/src/quadratic-sample.ts b/packages/geom-splines/src/quadratic-sample.ts new file mode 100644 index 0000000000..50d3ff6828 --- /dev/null +++ b/packages/geom-splines/src/quadratic-sample.ts @@ -0,0 +1,33 @@ +import { isNumber, isPlainObject } from "@thi.ng/checks"; +import { DEFAULT_SAMPLES, Sampler, SamplingOpts } from "@thi.ng/geom-resample"; +import { + mixQuadratic, + ReadonlyVec, + set, + Vec +} from "@thi.ng/vectors"; + +export const sampleQuadratic = + (points: ReadonlyVec[], opts?: number | Partial) => { + if (isPlainObject(opts) && (opts).dist !== undefined) { + return new Sampler(sampleQuadratic(points, (opts).num || DEFAULT_SAMPLES)) + .sampleUniform((opts).dist, (opts).last !== false); + } + opts = isNumber(opts) ? + { + num: opts, + last: true + } : + { + num: DEFAULT_SAMPLES, + ...opts + }; + const res: Vec[] = []; + const delta = 1 / opts.num; + const [a, b, c] = points; + for (let t = 0; t < opts.num; t++) { + res.push(mixQuadratic([], a, b, c, t * delta)); + } + opts.last && res.push(set([], c)); + return res; + }; diff --git a/packages/geom-splines/src/quadratic-split.ts b/packages/geom-splines/src/quadratic-split.ts new file mode 100644 index 0000000000..768a34aad0 --- /dev/null +++ b/packages/geom-splines/src/quadratic-split.ts @@ -0,0 +1,41 @@ +import { minError } from "@thi.ng/math"; +import { + distSq, + mixN, + mixQuadratic, + ReadonlyVec, + set +} from "@thi.ng/vectors"; + +export const quadraticSplitAt = + (a: ReadonlyVec, b: ReadonlyVec, c: ReadonlyVec, t: number) => { + if (t <= 0 || t >= 1) { + const p = t <= 0 ? a : c; + const c1 = [set([], p), set([], p), set([], p)]; + const c2 = [set([], a), set([], b), set([], c)]; + return t <= 0 ? [c1, c2] : [c2, c1]; + } + const ab = mixN([], a, b, t); + const bc = mixN([], b, c, t); + const p = mixN([], ab, bc, t); + return [[set([], a), ab, p], [p, bc, set([], c)]]; + }; + +export const quadraticSplitNearPoint = ( + p: ReadonlyVec, + a: ReadonlyVec, + b: ReadonlyVec, + c: ReadonlyVec, + res?: number, + iter?: number +) => + quadraticSplitAt( + a, b, c, + minError( + (t: number) => mixQuadratic([], a, b, c, t), + distSq, + p, + res, + iter + ) + ); diff --git a/packages/geom-splines/test/index.ts b/packages/geom-splines/test/index.ts new file mode 100644 index 0000000000..14c7198207 --- /dev/null +++ b/packages/geom-splines/test/index.ts @@ -0,0 +1,6 @@ +// import * as assert from "assert"; +// import * as gs from "../src/index"; + +describe("geom-splines", () => { + it("tests pending"); +}); diff --git a/packages/geom-splines/test/tsconfig.json b/packages/geom-splines/test/tsconfig.json new file mode 100644 index 0000000000..f6e63560dd --- /dev/null +++ b/packages/geom-splines/test/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../build", + "module": "commonjs" + }, + "include": [ + "./**/*.ts", + "../src/**/*.ts" + ] +} diff --git a/packages/geom-splines/tsconfig.json b/packages/geom-splines/tsconfig.json new file mode 100644 index 0000000000..893b9979c5 --- /dev/null +++ b/packages/geom-splines/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + "module": "es6", + "target": "es6" + }, + "include": [ + "./src/**/*.ts" + ] +}