Comparing version 1.6.0 to 2.0.0
127
index.js
@@ -1,126 +0,1 @@ | ||
exports.Transform = require('./lib/Transform') | ||
exports.estimateI = require('./lib/estimateI') | ||
exports.estimateL = require('./lib/estimateL') | ||
exports.estimateX = require('./lib/estimateX') | ||
exports.estimateY = require('./lib/estimateY') | ||
exports.estimateT = require('./lib/estimateT') | ||
exports.estimateS = require('./lib/estimateS') | ||
exports.estimateR = require('./lib/estimateR') | ||
exports.estimateTS = require('./lib/estimateTS') | ||
exports.estimateTR = require('./lib/estimateTR') | ||
exports.estimateSR = require('./lib/estimateSR') | ||
exports.estimateTSR = require('./lib/estimateTSR') | ||
exports.version = require('./lib/version') | ||
exports.create = function (scale, rotation, tx, ty) { | ||
// Create a nudged.Transform instance by using more meaningful parameters | ||
// than directly calling 'new nudged.Transform(...)' | ||
// | ||
// Parameters: | ||
// scale | ||
// number, the scaling factor | ||
// rotation | ||
// number, rotation in radians from positive x axis towards pos. y axis. | ||
// tx | ||
// translation toward pos. x | ||
// ty | ||
// translation toward pos. y | ||
if (typeof scale !== 'number') { scale = 1 } | ||
if (typeof rotation !== 'number') { rotation = 0 } | ||
if (typeof tx !== 'number') { tx = 0 } | ||
if (typeof ty !== 'number') { ty = 0 } | ||
var s = scale * Math.cos(rotation) | ||
var r = scale * Math.sin(rotation) | ||
return new exports.Transform(s, r, tx, ty) | ||
} | ||
exports.createFromArray = function (arr) { | ||
// Create a nudged.Transform instance from an array that was | ||
// previously created with nudged.Transform#toArray(). | ||
// | ||
// Together with nudged.Transform#toArray(), this method makes an easy | ||
// serialization and deserialization to and from JSON possible. | ||
// | ||
// Parameter: | ||
// arr | ||
// array with four elements | ||
var s = arr[0] | ||
var r = arr[1] | ||
var tx = arr[2] | ||
var ty = arr[3] | ||
return new exports.Transform(s, r, tx, ty) | ||
} | ||
// Expressions for createFromString | ||
var cssMatrix = /\s*matrix\(([-+\w ,.%]+)\)/ | ||
var cssMatrixDelimiter = /\s*,\s*/ | ||
exports.createFromString = function (str) { | ||
// Create a nudged.Transform instance from a string that uses | ||
// the CSS transform matrix syntax: 'matrix(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)' | ||
// | ||
// Together with nudged.Transform#toString(), this method makes an easy | ||
// serialization and deserialization to and from strings. | ||
// | ||
// Note that the function does not yet support other CSS transform functions | ||
// such as 'translate' or 'perspective'. It might also give unexpected | ||
// results if the matrix exhibits shear or non-uniform scaling. | ||
// | ||
// Parameter: | ||
// str | ||
// string, the transform description | ||
// | ||
// Return | ||
// a Transform | ||
// | ||
// Throws | ||
// if no valid transform is found | ||
// | ||
var matrixMatch = str.match(cssMatrix) | ||
if (!matrixMatch) { | ||
throw new Error('Invalid CSS matrix string: ' + str) | ||
} | ||
var elemStrings = matrixMatch[1].split(cssMatrixDelimiter) | ||
var elems = [] | ||
var i | ||
for (i = 0; i < elemStrings.length; i += 1) { | ||
elems.push(parseFloat(elemStrings[i])) | ||
} | ||
var s = elems[0] | ||
var r = elems[1] | ||
// skip [2][3] => forget shear or non-uniform scaling | ||
var tx = elems[4] | ||
var ty = elems[5] | ||
return new exports.Transform(s, r, tx, ty) | ||
} | ||
exports.estimate = function (type, domain, range, pivot) { | ||
// Parameter | ||
// type | ||
// string. One of the following: | ||
// 'I', 'L', 'X', 'Y', 'T', 'S', 'R', 'TS', 'TR', 'SR', 'TSR' | ||
// domain | ||
// array of 2d arrays | ||
// range | ||
// array of 2d arrays | ||
// pivot | ||
// An optional 2d array for 'S', 'R', and 'SR'. An angle for 'L'. | ||
// | ||
var name = 'estimate' + type.toUpperCase() | ||
try { | ||
return exports[name](domain, range, pivot) | ||
} catch (e) { | ||
if (typeof exports[name] !== 'function') { | ||
throw new Error('Unknown estimator type: ' + type) | ||
} | ||
throw e | ||
} | ||
} | ||
module.exports = require('./lib/index') |
// generated by genversion | ||
module.exports = '1.6.0' | ||
module.exports = '2.0.0' |
{ | ||
"name": "nudged", | ||
"version": "1.6.0", | ||
"version": "2.0.0", | ||
"description": "Affine transformation estimator e.g. for multi-touch gestures and calibration", | ||
"keywords": [ | ||
"geometry", | ||
"transformation", | ||
@@ -15,2 +16,3 @@ "scale", | ||
"estimator", | ||
"estimation", | ||
"similarity", | ||
@@ -30,13 +32,13 @@ "calibration" | ||
"license": "MIT", | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"browserify": "^15.0.0", | ||
"async": "^3.2.0", | ||
"browserify": "^17.0.0", | ||
"component-emitter": "^1.2.0", | ||
"genversion": "^2.0.1", | ||
"genversion": "^3.0.0", | ||
"hammerjs": "^2.0.4", | ||
"loadimages": "^1.0.0", | ||
"lodash": "^4.17.2", | ||
"mocha": "^4.1.0", | ||
"should": "^13.2.0", | ||
"standard": "^14.3.4" | ||
"lodash": "^4.17.21", | ||
"standard": "^16.0.3", | ||
"tap-spec": "^5.0.0", | ||
"tape": "^5.2.2" | ||
}, | ||
@@ -47,6 +49,8 @@ "scripts": { | ||
"test": "npm run test:lint && npm run test:unit", | ||
"test:unit": "mocha test/index.test.js", | ||
"test:unit": "node test/index.test.js | tap-spec", | ||
"test:lint": "standard index.js 'lib/**/*.js' 'test/**/*.js'", | ||
"test:lint:fix": "standard --fix index.js 'lib/**/*.js' 'test/**/*.js'", | ||
"build:docs": "node doc/gendocs/index.js", | ||
"gv": "genversion lib/version.js", | ||
"release": "npm run gv && npm test && npm run build:docs && npm publish", | ||
"test:nudged-editor": "standard --fix 'examples/nudged-editor/lib/*.js'", | ||
@@ -53,0 +57,0 @@ "build:nudged-editor": "npm run test:nudged-editor && browserify examples/nudged-editor/lib/index.js -o examples/nudged-editor/dist/app.js", |
453
README.md
@@ -6,2 +6,4 @@ # nudged | ||
![Nudged logo](doc/nudged-logo-2021-512.png) | ||
*Nudged* is **a JavaScript module** to efficiently estimate translation, scale, and/or rotation between two sets of 2D points. It has already been applied to **user interfaces, multi-touch recognition, geography, and eye tracker calibration**. | ||
@@ -11,7 +13,7 @@ | ||
- [Installation](#installation) | ||
- [Introduction](#introduction) | ||
- [Installation](#installation) | ||
- [Usage](#usage) | ||
- [Example apps](#example-apps) | ||
- [API](#api) | ||
- [API docs](doc/API.md) | ||
- [For developers](#for-developers) | ||
@@ -23,381 +25,260 @@ - [Acknowledgments](#acknowledgments) | ||
## Introduction | ||
## Installation | ||
In general, you can apply Nudged in any situation where you want to capture a 2D transformation based on a movement of any number of control points. See the image below for available types of transforms Nudged can estimate. | ||
Install `nudged` with [npm](https://www.npmjs.com/package/nudged) or other compatible package manager. The package comes in two flavors: functional and object oriented. | ||
<img src="doc/transformation-types.jpg" alt="Types of transformation estimators"/><br> | ||
_**Image**: Available types of transformation estimators. Each estimator has an abbreviated name, for example 'SR'. The black-white dots and connecting arrows represent movement of two control points. Given the control points, Nudged estimates a transformation. The image pairs represent the effect of the resulting transformation. To emphasize the effect, the control points and the initial image positions are kept the same for each type._ | ||
Install the functional nudged 2.0.0-beta: | ||
**Mathematically speaking**, Nudged is a set of optimal [least squares estimators](https://en.wikipedia.org/wiki/Least_squares) for nonreflective similarity transformation matrices. Such transformations are [affine transformations](https://en.wikipedia.org/wiki/Affine_transformation) with translation, rotation, and/or uniform scaling, and without reflection or shearing. The estimation has [time complexity](https://en.wikipedia.org/wiki/Time_complexity) of O(*n*), where *n* is the cardinality (size) of the point sets. In other words, Nudged solves a 2D to 2D point set registration problem (alias [Procrustes superimposition](https://en.wikipedia.org/wiki/Procrustes_analysis)) in [linear time](https://en.wikipedia.org/wiki/Time_complexity#Linear_time). The algorithms and their efficiency are thoroughly described in a **M.Sc. thesis** [Advanced algorithms for manipulating 2D objects on touch screens](http://URN.fi/URN:NBN:fi:tty-201605264186). | ||
**The development has been supported** by [Infant Cognition Laboratory](https://www.tuni.fi/en/research/infant-cognition) at [Tampere University](https://www.tuni.fi/en/) where Nudged is used to correct eye tracking data. Yet, the main motivation for Nudged comes from [Tapspace](https://github.com/taataa/tapspace), a zoomable user interface library where smooth and fast scaling by touch is crucial. | ||
## Installation | ||
With [npm](https://www.npmjs.com/package/nudged): | ||
$ npm install nudged | ||
Available also [in Python](https://pypi.python.org/pypi/nudged). | ||
Install the object-oriented [nudged 1.x](https://github.com/axelpale/nudged/tree/1.x): | ||
$ npm install nudged@1 | ||
## Usage | ||
Nudged is also available [in Python](https://pypi.python.org/pypi/nudged). | ||
Let `domain` and `range` be point sets before and after transformation as illustrated in the figure below: | ||
> var domain = [[0,0], [2,0], [ 1,2]] | ||
> var range = [[1,1], [1,3], [-1,2]] | ||
## Introduction | ||
<img src="https://rawgit.com/axelpale/nudged/master/doc/simple-example-pointset.png" alt="The transformation" width="500"/> | ||
In general, you can apply Nudged in any situation where you want to capture a 2D transformation based on a movement of any number of control points. See the image below for the available transformations Nudged can estimate. | ||
_**Figure**: Left: the domain. Center: the range. Right: the domain after transformation._ | ||
<img src="doc/transformation-types.jpg" alt="Types of transformation estimators"/><br> | ||
_**Image**: Available transformation estimators. Each estimator has an abbreviated name, for example 'SR', according to the free parameters to estimate. The black-white dots and connecting arrows represent movement of two control points. Given the control points, Nudged estimates a transformation. The pairs of photos represent the effect of the resulting transformation. For easy visual comparison, the control points and the initial image positions are kept the same for each estimator._ | ||
Compute an optimal transformation based on the points: | ||
**Mathematically speaking**, Nudged is a set of optimal [least squares estimators](https://en.wikipedia.org/wiki/Least_squares) for the group of nonreflective similarity transformation matrices, also called [Helmert transformations](https://en.wikipedia.org/wiki/Helmert_transformation). Such transformations are [affine transformations](https://en.wikipedia.org/wiki/Affine_transformation) with translation, rotation, and/or uniform scaling, and without reflection or shearing. The estimation has [time complexity](https://en.wikipedia.org/wiki/Time_complexity) of O(*n*), where *n* is the cardinality (size) of the point sets. In other words, Nudged solves a 2D to 2D point set registration problem (alias [Procrustes superimposition](https://en.wikipedia.org/wiki/Procrustes_analysis)) in [linear time](https://en.wikipedia.org/wiki/Time_complexity#Linear_time). The algorithms and their efficiency are thoroughly described in a **M.Sc. thesis** [Advanced algorithms for manipulating 2D objects on touch screens](http://URN.fi/URN:NBN:fi:tty-201605264186). | ||
> var trans = nudged.estimate('TSR', domain, range) | ||
**The development has been supported** by [Infant Cognition Laboratory](https://www.tuni.fi/en/research/infant-cognition) at [Tampere University](https://www.tuni.fi/en/) where Nudged is used to correct eye tracking data. Yet, the main motivation for Nudged comes from [Tapspace](https://github.com/taataa/tapspace), a zoomable user interface library where smooth and fast scaling by touch is crucial. | ||
Examine the transformation matrix: | ||
> trans.getMatrix() | ||
{ a: 0, c: -1, e: 1, | ||
b: 1, d: 0, f: 1 } | ||
> trans.getRotation() | ||
1.5707... = π / 2 | ||
> trans.getScale() | ||
1.0 | ||
> trans.getTranslation() | ||
[1, 1] | ||
## Usage | ||
Apply the transformation to other points: | ||
Let `domain` be a set of points, `[{ x, y }, ...]`. Let `range` be the same points after an unknown transformation T as illustrated in the figure below. | ||
> trans.transform([2,2]) | ||
[-1,3] | ||
const domain = [{ x: 0, y: 2 }, { x: 2, y: 2 }, { x: 1, y: 4 }] | ||
const range = [{ x: 4, y: 4 }, { x: 4, y: 2 }, { x: 6, y: 3 }] | ||
Inverse the transformation: | ||
<img src="doc/img/nudged-diagram-6-4-domain-range.png" alt="The domain and the range" /> | ||
> var inv = trans.inverse() | ||
> inv.transform([-1,3]) | ||
[2,2] | ||
_**Figure**: The domain (circles o) and the range (crosses x). The + marks the point {x:0,y:0}._ | ||
See [API](#api) for more. | ||
We would like to find a simple 2D transformation `tran` that simulates T as closely as possible by combining translation, scaling, and rotation. We compute `tran` by calling [nudged.estimate](doc/API.md#nudgedestimate): | ||
### Using pivoted transformations | ||
const tran = nudged.estimate({ | ||
estimator: 'TSR', | ||
domain: domain, | ||
range: range | ||
}) | ||
You can think the pivot point as a pin pushed through a paper. The pin keeps its location intact regardless of the transformation around it, as illustrated in the figure below. | ||
The result is a *transform* object: | ||
<img src="https://rawgit.com/axelpale/nudged/master/doc/simple-example-fixed.png" alt="A fixed point transformation" width="500"/> | ||
> tran | ||
{ a: 0, b: -1, x: 4, y: 4 } | ||
_**Figure**: Left: a black pivot point and the domain. Center: the range. Right: the pivot and the domain after transformation._ | ||
You can apply `tran` to a point with [point.transform](doc/API.md#nudgedpointtransform): | ||
In the following example we estimate an optimal scaling and rotation around point `[-1,0]`: | ||
> nudged.point.transform({ x: 0, y: 4 }, tran) | ||
{ x: 6, y: 4 } | ||
> var pivot = [-1,0] | ||
> var domain = [[0,0], [2,0], [ 1,2]] | ||
> var range = [[1,1], [1,3], [-1,2]] | ||
> var pivotTrans = nudged.estimate('SR', domain, range, pivot) | ||
<img src="doc/img/nudged-diagram-6-7-point-transform.png" alt="A point is being transformed" /> | ||
If we now apply the transformation to the domain, we see that the result is close to the range. Also, if we apply it to the pivot, the point stays the same. | ||
_**Figure**: A point {x:0, y:4} is transformed by the estimated transform._ | ||
> pivotTrans.transform(domain) | ||
[[-0.33, 0.77], [0.99, 2.33], [-1.22, 2.88]] | ||
> pivotTrans.transform(pivot) | ||
[-1,0] | ||
You can apply `tran` to other geometric shapes as well, for example to correct the orientation based on some sensor data. In the case of HTML image elements, just convert `tran` to a [CSS transform](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function) string with [transform.toString](doc/API.md#nudgedtransformtostring): | ||
> img.style.transform = nudged.transform.toString(tran) | ||
## Example apps | ||
<img src="doc/img/nudged-diagram-10-2-photo-transform.jpg" alt="A photograph is being transformed" /> | ||
The following demo applications give an example how nudged can be used in web. | ||
_**Figure**: An HTML image before and after the transform we estimated from the points._ | ||
### Multitouch transformation with N fingers | ||
The [nudged.transform](doc/API.md#nudgedtransform) module provides | ||
lots of tools to process transform objects. | ||
For example, to make a transformation that maps the range back to the domain | ||
instead of another way around, invert the transform with [transform.inverse](doc/API.md#nudgedtransforminverse): | ||
[<img src="https://rawgit.com/axelpale/nudged/master/examples/nudged-gesture/screenshot.jpg" alt="Four hands transforming the image simultaneously" width="600"/>](https://rawgit.com/axelpale/nudged/master/examples/nudged-gesture/index.html) | ||
> const inv = nudged.transform.inverse(tran) | ||
> nudged.point.transform({ x: 6, y: 4 }, inv) | ||
{ x: 0, y: 4 } | ||
The [**touch gesture demo**](https://rawgit.com/axelpale/nudged/master/examples/nudged-gesture/index.html) takes the common pinch-zoom and rotate gestures a step further. Many multitouch apps allow you to scale and rotate with two fingers. However, usually the additional fingers are ignored. But what if one wants to use, say, both hands and all the fingers on a huge touchscreen? | ||
<img src="doc/img/nudged-diagram-6-8-transform-inverse.png" alt="A point transformed by the inverse of the estimate." /> | ||
For reference, the [**typical gesture demo**](https://rawgit.com/axelpale/nudged/master/examples/typical-gesture/index.html) implements similar demo with the popular [Hammer.js](http://hammerjs.github.io/) touch gesture library. As you can experience, only the first two pointers are regarded for scaling and rotation. | ||
_**Figure**: A point is transformed by the inverse of the estimated transform._ | ||
### Point set editor | ||
See [nudged.transform](doc/API.md#nudgedtransform) for more tools and details. | ||
[<img src="https://rawgit.com/axelpale/nudged/master/examples/nudged-editor/screenshot.png" alt="Nudged editor screenshot" width="600"/>](https://rawgit.com/axelpale/nudged/master/examples/nudged-editor/index.html) | ||
### Set a center point | ||
The [**editor demo**](https://rawgit.com/axelpale/nudged/master/examples/nudged-editor/index.html) allows you to add domain and range points on a surface and explore how the points affect the transformation. | ||
To estimate scalings and rotations around a fixed point, give an additional `center` parameter. Only the estimators `S`, `R`, and `SR` respect the `center` parameter. | ||
### Tokyo metro map viewer | ||
const center = { x: 4 , y: 0 } | ||
const rotateAround = nudged.estimate({ | ||
estimator: 'R', | ||
domain: domain, | ||
range: range, | ||
center: center | ||
}) | ||
[<img src="https://rawgit.com/axelpale/nudged/master/examples/nudged-map/screenshot.png" alt="A screenshot of Nudged map viewer example" width="600"/>](https://rawgit.com/axelpale/nudged/master/examples/nudged-map/index.html) | ||
You can think the center point as a nail that keeps an elastic sheet of rubber fixed onto a table. The nail retains its location regardless of how the rubber sheet is rotated or stretched around it. | ||
In this [map viewer demo](https://rawgit.com/axelpale/nudged/master/examples/nudged-map/index.html), nudged is used to recognize multi-touch gestures to scale, rotate, and translate [a large image](https://commons.wikimedia.org/wiki/File:Tokyo_metro_map.png) on HTML5 canvas. | ||
<img src="doc/img/nudged-diagram-7-4-rotation-around-center.png" alt="A rotation around a fixed center point" /> | ||
_**Figure**: Rotation around a center point (⊕) maps the domain (o) as close to the range (x) as possible. Here the mapped image (●) cannot match the range exactly due to the restriction set by the center point. The + denotes the point {x:0, y:0}._ | ||
## API | ||
To test the resulting transform, we can apply it to the center point and observe that the point stays the same. | ||
Nudged API provides a class `Transform` to represent a nonreflective similarity transformation matrix and multiple functions to estimate such transformations from sets of points. | ||
> nudged.point.transform(center, rotateAround) | ||
{ x: 4, y: 0 } | ||
### nudged.create(scale, rotation, translationX, translationY) | ||
To estimate scalings in respect of a center point, as illustrated below, set `estimators: 'S'`. This scaling operation is also called a [homothety](https://en.wikipedia.org/wiki/Homothety). | ||
Create a transformation that scales, rotates, and translates as specified. | ||
const s = nudged.estimate({ | ||
estimator: 'S', | ||
domain: domain, | ||
range: range, | ||
center: center | ||
}) | ||
**Parameters:** | ||
- *scale*: a number; the scaling factor. | ||
- *rotation*: a number; the rotation in radians from positive x axis toward positive y axis. | ||
- *translationX*: a number; translation after rotation, toward positive x axis. | ||
- *translationY*: a number; translation after rotation, toward positive y axis. | ||
<img src="doc/img/nudged-diagram-8-8-scaling-estimation.png" alt="Scaling about a center point (⊕)" /> | ||
The parameters are optional and default to the identity transformation. | ||
_**Figure**: The domain (o) is scaled towards the center point (⊕) so that the resulting image (●) lies as close to the range (x) as possible._ | ||
**Return** a new `nudged.Transform` instance. | ||
See [estimators.S](doc/API.md#nudgedestimatorss), [estimators.R](doc/API.md#nudgedestimatorss), and [estimators.SR](doc/API.md#nudgedestimatorss) for further details. | ||
**Examples:** | ||
### Analyse the transform | ||
> var t0 = nudged.create() | ||
> t0.transform([3, 1]) | ||
[3, 1] | ||
To examine properties of the resulting transformation matrix: | ||
> var t1 = nudged.create(2) | ||
> t1.transform([3, 1]) | ||
[6, 2] | ||
> nudged.transform.getRotation(tran) | ||
-1.5707... = -π / 2 | ||
> nudged.transform.getScale(tran) | ||
1.0 | ||
> nudged.transform.getTranslation(tran) | ||
{ x: 2, y: 4 } | ||
> nudged.transform.toMatrix(tran) | ||
{ a: 0, c: 1, e: 2, | ||
b: -1, d: 0, f: 4 } | ||
> var t2 = nudged.create(1, Math.PI / 2) | ||
> t2.transform([3, 1]) | ||
[-1, 3] | ||
To compare how well the transform fits the domain to the range, you can compute | ||
the *mean squared error*, *MSE*. The smaller the error, the better the fit: | ||
> var t3 = nudged.create(1, 0, 20.2, 0) | ||
> t3.transform([3, 1]) | ||
[23.2, 1] | ||
> nudged.analysis.mse(tran, domain, range) | ||
0 | ||
### nudged.createFromArray(arr) | ||
The MSE of 0 means that the estimate maps domain on the range perfectly. | ||
We can demonstrate this by transforming the domain points and | ||
comparing the result to the range: | ||
Create a `nudged.Transform` instance from an array created by nudged.Transform#toArray(). Together with `nudged.Transform#toArray()` this method makes an easy **serialization and deserialization** to and from JSON possible. | ||
> nudged.point.transformMany(domain, tran) | ||
[ { x: 4, y: 4 }, { x: 4, y: 2 }, { x: 6, y: 3 } ] | ||
> range | ||
[ { x: 4, y: 4 }, { x: 4, y: 2 }, { x: 6, y: 3 } ] | ||
> var t1 = nudged.create(1, 2, 3, 4) | ||
> var arr = trans.toArray() | ||
> var t2 = nudged.createFromArray(arr) | ||
> t1.equals(t2) | ||
true | ||
<img src="doc/img/nudged-diagram-6-6-transform-domain.png" alt="Scaling about a center point (⊕)" /> | ||
### nudged.createFromString(str) | ||
_**Figure**: The domain (o) mapped with `tran` (→). The fit is perfect, the image (●) matches the range (x) exactly._ | ||
Create a `nudged.Transform` instance from a CSS transform matrix string. Compatible with `nudged.Transform#toString()`. | ||
See [nudged.analysis](doc/API.md#nudgedanalysis) for more. | ||
> var css = 'matrix(1.0, 2.0, -2.0, 1.0, 3.0, 4.0)' | ||
> var t = nudged.createFromString(css) | ||
### Build transforms | ||
### nudged.estimate(type, domain, range, param?) | ||
In addition to estimation, you can create transforms by other means. For example, let `t` be a 0.5x scaling towards `{ x: 6, y: 5 }`: | ||
Compute an optimal affine transformation from *domain* to *range* points. The *type* of transformation determines the freedom of the transformation to be estimated. | ||
> const t = nudged.transform.scaling({ x: 6, y: 5 }, 0.5) | ||
> t | ||
{ a: 0.5, b: 0, x: 3, y: 2.5 } | ||
**Available types** | ||
Let us apply `t` to `domain`. The result is illustrated below. | ||
- `I`: Identity transform. Whatever the points, returns always the identity transformation. | ||
- `L`: Translation along line. Takes additional *angle* parameter in radians. Direction of the angle is from positive x-axis towards positive y-axis. | ||
- `X`: Horizontal translation. Equivalent to `L` with angle 0. | ||
- `Y`: Vertical translation. Equivalent to `L` with angle ±PI/2. | ||
- `T`: Free translation. | ||
- `S`: Scaling about a fixed *pivot* point. | ||
- `R`: Rotation around a fixed *pivot* point. | ||
- `TS`: Free translation with scaling. | ||
- `TR`: Free translation with rotation. | ||
- `SR`: Scaling and rotation around a fixed *pivot* point. | ||
- `TSR`: Free translation with both scaling and rotation. | ||
> nudged.point.transformMany(domain, t) | ||
[ { x: 3, y: 3.5 }, { x: 4, y: 3.5 }, { x: 3.5, y: 4.5 } ] | ||
**Parameters:** | ||
- *type*: string. The freedom of the transformation. Must be one of the following: `'I'`, `'L'`, `'X'`, `'Y'`, `'T'`, `'S'`, `'R'`, `'TS'`, `'TR'`, `'SR'`, `'TSR'` | ||
- *domain*: array of [x,y] points. The source point set. | ||
- *range*: array of [x,y] points. The target point set. | ||
- *param*: For types `S`, `R`, and `SR` this is an optional `[x,y]` pivot point that defaults to the origin `[0,0]`. For type `L` this is an angle in radians so that angle PI/2 (90 deg) is towards positive y-axis. | ||
<img src="doc/img/nudged-diagram-8-4-scaling-by-half.png" alt="Scaling about a center point (⊕)" /> | ||
The *domain* and *range* should have equal length. Different lengths are allowed but additional points in the longer array are ignored. | ||
_**Figure**: Scaling the domain (o) by the factor of 0.5 about the center point (⊕). The resulting image (●) has all distances halved. The + denotes the point {x:0, y:0}._ | ||
**Return** new `nudged.Transform(...)` instance. | ||
Then let us modify the transform `t` further. Let `tr` be a transform that combines `t` | ||
with a negative rotation of 45 degrees (π/4) around `{ x: 0, y: 0 }`: | ||
You can also call the estimators directly for slightly enhanced performance: | ||
> const tr = nudged.transform.rotateBy(t, { x: 0, y: 0 }, -Math.PI / 4) | ||
> tr | ||
{ a: 0.353..., b: -0.353..., x: 3.889..., y: -0.353... } | ||
- `nudged.estimateI()` | ||
- `nudged.estimateL(domain, range, angle)` | ||
- `nudged.estimateX(domain, range)` | ||
- `nudged.estimateY(domain, range)` | ||
- `nudged.estimateT(domain, range)` | ||
- `nudged.estimateS(domain, range, pivot)` | ||
- `nudged.estimateR(domain, range, pivot)` | ||
- `nudged.estimateTS(domain, range)` | ||
- `nudged.estimateTR(domain, range)` | ||
- `nudged.estimateSR(domain, range, pivot)` | ||
- `nudged.estimateTSR(domain, range)` | ||
Let us apply the resulting transform to the domain points. The result is illustrated below. | ||
**Example:** | ||
> nudged.point.transformMany(domain, tr) | ||
[ | ||
{ x: 4.596..., y: 0.353... }, | ||
{ x: 5.303..., y: -0.353... }, | ||
{ x: 5.656..., y: 0.707... } | ||
] | ||
> var domain = [[0,0], [2,0], [ 1,2]] | ||
> var range = [[1,1], [1,3], [-1,2]] | ||
> var tr = nudged.estimate('SR', domain, range) | ||
> tr.getScale() | ||
1.242259 | ||
> tr.getRotation() | ||
1.107148 | ||
<img src="doc/img/nudged-diagram-7-9-combine-rotation.png" alt="A scaling combined with a rotation" /> | ||
### nudged.version | ||
_**Figure**: A scaling is combined with rotation so that the image of the scaling (grey ●) is further rotated by 90 degrees around a center point (⊕)._ | ||
Contains the module version string identical to the version in *package.json*. | ||
> nudged.version | ||
'1.2.3' | ||
Not all transformation need to be built. You can find some prebuilt transforms under [nudged.transform](doc/API.md#nudgedtransform): | ||
### nudged.Transform(s, r, tx, ty) | ||
> const p = { x: 4, y: 2 } | ||
> const X2 = nudged.transform.X2 | ||
> nudged.point.transform(p, X2) | ||
{ x: 8, y: 4 } | ||
> const ROT180 = nudged.transform.ROT180 | ||
> nudged.point.transform(p, ROT180) | ||
{ x: -4, y: -2 } | ||
> const I = nudged.transform.IDENTITY | ||
> nudged.point.transform(p, I) | ||
{ x: 4, y: 2 } | ||
A constructor for a nonreflective similarity transformation. You usually do not need to call it directly because both `nudged.create(...)` and `nudged.estimate(...)` create and return instances for you. Nevertheless, if you need to create one: | ||
To discover more features and details, see [API](doc/API.md). | ||
> var trans = new nudged.Transform(0.5, 0, 20, 0) | ||
## Example apps | ||
The `nudged.Transform` instance is designed to be immutable. | ||
The following demo applications give an example how nudged can be used in the web. | ||
**Parameters** `s`, `r`, `tx`, and `ty` define the elements of an [augmented transformation matrix](https://en.wikipedia.org/wiki/Affine_transformation#Augmented_matrix) in the following manner: | ||
### Multitouch transformation with N fingers | ||
| s -r tx | | ||
| r s ty | | ||
| 0 0 1 | | ||
[<img src="https://rawgit.com/axelpale/nudged/master/examples/nudged-gesture/screenshot.jpg" alt="Four hands transforming the image simultaneously" width="600"/>](https://rawgit.com/axelpale/nudged/master/examples/nudged-gesture/index.html) | ||
Note that `s` and `r` do **not** represent scaling and rotation but instead `s = scalingFactor * Math.cos(rotationRads)` and `r = scalingFactor * Math.sin(rotationRads)`. The parameters `tx` and `ty` represent horizontal and vertical translation after rotation. | ||
The [**touch gesture demo**](https://rawgit.com/axelpale/nudged/master/examples/nudged-gesture/index.html) takes the common pinch-zoom and rotate gestures a step further. Many multitouch apps allow you to scale and rotate with two fingers. However, usually the additional fingers are ignored. But what if one wants to use, say, both hands and all the fingers on a huge touchscreen? | ||
### nudged.Transform.IDENTITY | ||
For reference, the [**typical gesture demo**](https://rawgit.com/axelpale/nudged/master/examples/typical-gesture/index.html) implements similar demo with the popular [Hammer.js](http://hammerjs.github.io/) touch gesture library. As you can experience, only the first two pointers are regarded for scaling and rotation. | ||
A default instance of `nudged.Transform` that represents the identity transformation `new Transform(1, 0, 0, 0)` i.e. transformation without an effect. You can use it in building new transformations: | ||
### Point set editor | ||
> var trans = nudged.Transform.IDENTITY.scaleBy(0.6).rotateBy(0.3); | ||
[<img src="https://rawgit.com/axelpale/nudged/master/examples/nudged-editor/screenshot.png" alt="Nudged editor screenshot" width="600"/>](https://rawgit.com/axelpale/nudged/master/examples/nudged-editor/index.html) | ||
### nudged.Transform.R90 .R180 .R270 .X2 | ||
The [**editor demo**](https://rawgit.com/axelpale/nudged/master/examples/nudged-editor/index.html) allows you to add domain and range points on a surface and explore how the points affect the transformation. | ||
Following prebuilt `Transform` instances are available: | ||
### Tokyo metro map viewer | ||
- `R90`: clockwise 90 degree rotation. Equal to `new Transform(0, 1, 0, 0)`. | ||
- `R180`: 180 degree rotation. Equal to `new Transform(-1, 0, 0, 0)`. | ||
- `R270`: counterclockwise 90 degree rotation. Equal to `new Transform(0, -1, 0, 0)`. | ||
- `X2`: scale up by the factor of two. Equal to `new Transform(2, 0, 0, 0)`. | ||
[<img src="https://rawgit.com/axelpale/nudged/master/examples/nudged-map/screenshot.png" alt="A screenshot of Nudged map viewer example" width="600"/>](https://rawgit.com/axelpale/nudged/master/examples/nudged-map/index.html) | ||
**Example:** | ||
In this [map viewer demo](https://rawgit.com/axelpale/nudged/master/examples/nudged-map/index.html), nudged is used to recognize multi-touch gestures to scale, rotate, and translate [a large image](https://commons.wikimedia.org/wiki/File:Tokyo_metro_map.png) on HTML5 canvas. | ||
> nudged.Transform.X2.getScale() | ||
2 | ||
### nudged.Transform#s, #r, #tx, #ty | ||
## API | ||
Elements of the internal transformation matrix. Direct use of these properties is not recommended. | ||
[The functional 2.x API documentation](doc/API.md). | ||
> var t = nudged.create(2, Math.PI / 2, 10, 20) | ||
> t.s | ||
1.2246e-16 | ||
> t.r | ||
2 | ||
> t.tx | ||
10 | ||
> t.ty | ||
20 | ||
[The object-oriented 1.x API documentation](https://github.com/axelpale/nudged/tree/1.x#api) | ||
### nudged.Transform#almostEqual(tr, epsilon?) | ||
Compare equality of two transformations and allow small differences that likely occur due to floating point arithmetics. | ||
## For developers | ||
**Alias** `.almostEquals(tr)` | ||
Guidelines: | ||
**Parameter** `tr` is an instance of `nudged.Transform`. Optional parameter `epsilon` is a small number that defines largest allowed difference and defaults to `Transform.EPSILON`. The difference is computed as the sum of absolute differences of the properties s, r, tx, and ty. | ||
- ES6 | ||
- [Standard style](https://standardjs.com/) | ||
- 2 space indent | ||
- max 80 chars per line | ||
- spaces around operators | ||
- Functional approach | ||
- namespaces and functions instead of classes and methods | ||
- immutable and stateless data handling; no in-place manipulation. | ||
- Minimal run-time type checking | ||
- Nudged is designed to be a low-level module with high performance. | ||
- Instead of run-time checks, the geometries provide a dedicated .validate function. | ||
- Write rich comments that answer the question why. | ||
**Return** true if the parameters of the two transformations are equal or almost equal and false otherwise. | ||
### nudged.Transform#equal(tr) | ||
**Alias** `.equals(tr)` | ||
**Parameter** `tr` is an instance of `nudged.Transform`. | ||
**Return** true if the parameters of the two transformations are equal and false otherwise. | ||
### nudged.Transform#getMatrix() | ||
Get the transformation matrix in a format compatible with [kld-affine](https://www.npmjs.com/package/kld-affine). | ||
**Return** an object with properties `a`, `b`, `c`, `d`, `e`, and `f`. | ||
> trans.getMatrix() | ||
{ a: 0.48, c: -0.52, e: 205.04, | ||
b: 0.52, d: 0.48, f: 4.83 } | ||
The properties represent the following matrix: | ||
| a c e | | ||
| b d f | | ||
| 0 0 1 | | ||
### nudged.Transform#getRotation() | ||
Get clockwise rotation from the positive x-axis. | ||
**Return** rotation in radians. | ||
### nudged.Transform#getScale() | ||
**Return** scaling multiplier, e.g. `0.333` for a threefold shrink. | ||
### nudged.Transform#getTranslation() | ||
**Return** `[tx, ty]` where `tx` and `ty` denotes movement along x-axis and y-axis accordingly. | ||
### nudged.Transform#toArray() | ||
Together with `nudged.createFromArray(...)` this method makes an easy serialization and deserialization to and from JSON possible. | ||
**Return** an array representation of the transformation: `[s, r, tx, ty]`. Note that `s` and `r` do not represent scaling and rotation but elements of the matrix. | ||
### nudged.Transform#transform(points) | ||
Apply the transform to a point or an array of points. | ||
**Parameter** `points` is an array of points `[[x, y], ...]` or a single point `[x, y]`. | ||
**Return** an array of transformed points or single point if only a point was given. For example: | ||
> trans.transform([1,1]) | ||
[2,2] | ||
> trans.transform([[1,1]]) | ||
[[2,2]] | ||
> trans.transform([[1,1], [2,3]]) | ||
[[2,2], [3,4]] | ||
### nudged.Transform#inverse() | ||
**Return** a new `nudged.Transform` instance that is the inverse of the original transformation. | ||
**Throw** an `Error` instance if the transformation is singular and cannot be inversed. This occurs if the range points are all the same which forces the scale to drop to zero. | ||
### nudged.Transform#translateBy(dx, dy) | ||
**Return** a new `nudged.Transform` instance where the image of the original has been translated. | ||
### nudged.Transform#scaleBy(multiplier, pivot?) | ||
**Parameter** `multiplier` is a number. Optional parameter `pivot` is a point `[x, y]`. | ||
**Return** a new `nudged.Transform` instance where the image of the original has been scaled. | ||
The scaling is done around an optional pivot point that defaults to [0,0]. | ||
### nudged.Transform#rotateBy(radians, pivot?) | ||
**Parameter** `radians` is a number. Optional parameter `pivot` is a point `[x, y]`. | ||
**Return** a new `nudged.Transform` instance where the image of the original has been rotated. | ||
The rotation is done around an optional pivot point that defaults to [0,0]. | ||
### nudged.Transform#multiplyBy(tr) | ||
**Alias** `.multiplyRight(tr)` | ||
**Parameter** `tr` is an instance of `nudged.Transform`. | ||
**Return** a new `nudged.Transform` instance where the original transformation matrix is multiplied from the right with the transformation matrix of `tr`. | ||
The resulting transformation is equal to first transforming with `tr` and then with the instance. More precisely, the image of the resulting transformation is the image of `tr` transformed by the instance. | ||
## For developers | ||
Run lint & unit tests: | ||
@@ -439,3 +320,3 @@ | ||
- [Infant Cognition Laboratory at University of Tampere](https://www.tuni.fi/en/research/infant-cognition) and [Adj. Prof. Jukka Leppänen](https://scholar.google.fi/citations?user=dNRRUIsAAAAJ) for funding and support in research. | ||
- [3D Media Group at Tampere University of Technology](https://www.tuni.fi/en/research/3d-media-group), [M.Sc. Olli Suominen](https://tutcris.tut.fi/portal/en/persons/olli-suominen(8d7b7ce4-1468-4621-9a6e-d307f644c9bb).html), and [Assoc. Prof. Atanas Gotchev](https://tutcris.tut.fi/portal/en/persons/atanas-gotchev(3b4a825b-941b-484e-b046-cd09bde1cd31).html) for providing touch-screen devices for testing. | ||
- [3D Media Group at Tampere University of Technology](https://www.tuni.fi/en/research/3d-media-group), [M.Sc. Olli Suominen](https://researchportal.tuni.fi/en/persons/olli-suominen), and [Prof. Atanas Gotchev](https://researchportal.tuni.fi/en/persons/atanas-gotchev) for providing touch-screen devices for testing. | ||
- Tanja for math photos. | ||
@@ -453,2 +334,2 @@ - Vilkku, Xiao, and Krista for finger photos. | ||
[MIT Licence](../blob/master/LICENSE) | ||
[MIT Licence](LICENSE) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
65674
62
1751
10
331
1