From b8efe0ab44b1eee1adc3ab6b7909f06c2b586993 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 12 Oct 2022 16:04:46 +0300 Subject: [PATCH 01/41] test vec3.add --- test/vec3/add.test.js | 26 ++++++++++++++++++++++++++ test/vec3/index.test.js | 1 + 2 files changed, 27 insertions(+) create mode 100644 test/vec3/add.test.js diff --git a/test/vec3/add.test.js b/test/vec3/add.test.js new file mode 100644 index 00000000..9180eb00 --- /dev/null +++ b/test/vec3/add.test.js @@ -0,0 +1,26 @@ +const affineplane = require('../../index') +const vec3 = affineplane.vec3 + +module.exports = (ts) => { + ts.test('case: zeroes and ones', (t) => { + t.deepEqual( + vec3.add( + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: 0 } + ), + { x: 0, y: 0, z: 0 }, + 'zeroes' + ) + + t.deepEqual( + vec3.add( + { x: 1, y: 1, z: 1 }, + { x: 1, y: 1, z: 1 } + ), + { x: 2, y: 2, z: 2 }, + 'ones' + ) + + t.end() + }) +} diff --git a/test/vec3/index.test.js b/test/vec3/index.test.js index c5b451d0..cba65c0b 100644 --- a/test/vec3/index.test.js +++ b/test/vec3/index.test.js @@ -1,5 +1,6 @@ // A unit for each method. const units = { + add: require('./add.test'), cross: require('./cross.test'), difference: require('./difference.test'), divide: require('./divide.test'), From 683b7f8e697257e147fb6bc6eabc3dd28e791c69 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 12 Oct 2022 16:09:38 +0300 Subject: [PATCH 02/41] impl vec4 with .add .create .hamilton .norm .scaleBy --- lib/index.js | 3 +++ lib/vec4/add.js | 21 +++++++++++++++++++++ lib/vec4/create.js | 18 ++++++++++++++++++ lib/vec4/hamilton.js | 21 +++++++++++++++++++++ lib/vec4/index.js | 14 ++++++++++++++ lib/vec4/norm.js | 14 ++++++++++++++ lib/vec4/scaleBy.js | 21 +++++++++++++++++++++ test/index.test.js | 1 + test/vec4/add.test.js | 26 ++++++++++++++++++++++++++ test/vec4/hamilton.test.js | 18 ++++++++++++++++++ test/vec4/index.test.js | 11 +++++++++++ 11 files changed, 168 insertions(+) create mode 100644 lib/vec4/add.js create mode 100644 lib/vec4/create.js create mode 100644 lib/vec4/hamilton.js create mode 100644 lib/vec4/index.js create mode 100644 lib/vec4/norm.js create mode 100644 lib/vec4/scaleBy.js create mode 100644 test/vec4/add.test.js create mode 100644 test/vec4/hamilton.test.js create mode 100644 test/vec4/index.test.js diff --git a/lib/index.js b/lib/index.js index f4401144..87046d0e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -59,6 +59,9 @@ exports.vector2 = exports.vec2 exports.vec3 = require('./vec3') exports.vector3 = exports.vec3 +exports.vec4 = require('./vec4') +exports.quat = exports.vec4 + // affineplane.version // // Package version string, for example `'1.2.3'`. Uses semantic versioning. diff --git a/lib/vec4/add.js b/lib/vec4/add.js new file mode 100644 index 00000000..72f6b18d --- /dev/null +++ b/lib/vec4/add.js @@ -0,0 +1,21 @@ +module.exports = (v, w) => { + // affineplane.vec4.add(v, w) + // + // Add two quaternions together via vector addition. + // + // Parameters: + // v + // a vec4 + // w + // a vec4 + // + // Return + // a vec4 + // + return { + r: v.r + w.r, + x: v.x + w.x, + y: v.y + w.y, + z: v.z + w.z + } +} diff --git a/lib/vec4/create.js b/lib/vec4/create.js new file mode 100644 index 00000000..11cebc15 --- /dev/null +++ b/lib/vec4/create.js @@ -0,0 +1,18 @@ +module.exports = (r, x, y, z) => { + // affineplane.vec4.create(r, x, y, z) + // + // Parameters: + // r + // a number + // x + // a number + // y + // a number + // z + // a number + // + // Return + // a vec4 + // + return { r, x, y, z } +} diff --git a/lib/vec4/hamilton.js b/lib/vec4/hamilton.js new file mode 100644 index 00000000..bd368715 --- /dev/null +++ b/lib/vec4/hamilton.js @@ -0,0 +1,21 @@ +module.exports = (v, w) => { + // affineplane.vec4.hamilton(v, w) + // + // The Hamilton product of two quaternions. + // + // Parameters + // v + // a vec4 + // w + // a vec4 + // + // Return + // a vec4 + // + return { + r: v.r * w.r - v.x * w.x - v.y * w.y - v.z * w.z, + x: v.r * w.x + v.x * w.r + v.y * w.z - v.z * w.y, + y: v.r * w.y - v.x * w.z + v.y * w.r + v.z * w.x, + z: v.r * w.z + v.x * w.y - v.y * w.x + v.z * w.r + } +} diff --git a/lib/vec4/index.js b/lib/vec4/index.js new file mode 100644 index 00000000..001749d8 --- /dev/null +++ b/lib/vec4/index.js @@ -0,0 +1,14 @@ +// affineplane.vec4 +// affineplane.quat +// +// A vec4 is a 4D vector { r, x, y, z }. It can represent a quaternion +// and a rotating vector in 3D space, r being the radius or rotation at +// angle of 1 rad. +// + +exports.add = require('./add') +exports.hamilton = require('./hamilton') +exports.create = require('./create') +exports.norm = require('./norm') +exports.product = exports.combine +exports.scaleBy = require('./scaleBy') diff --git a/lib/vec4/norm.js b/lib/vec4/norm.js new file mode 100644 index 00000000..39fde2ce --- /dev/null +++ b/lib/vec4/norm.js @@ -0,0 +1,14 @@ +module.exports = (v) => { + // affineplane.vec4.norm(v) + // + // The length of 4D vector. Also called the norm. + // + // Parameters: + // v + // a vec4 + // + // Return + // a number + // + return Math.sqrt(v.r * v.r + v.x * v.x + v.y * v.y + v.z * v.z) +} diff --git a/lib/vec4/scaleBy.js b/lib/vec4/scaleBy.js new file mode 100644 index 00000000..311b6d00 --- /dev/null +++ b/lib/vec4/scaleBy.js @@ -0,0 +1,21 @@ +module.exports = (v, m) => { + // affineplane.vec4.scaleBy(v, m) + // + // Scalar multiplication of a vector. + // + // Parameters: + // v + // a vec4 + // m + // a number, a multiplier + // + // Return: + // a vec4 + // + return { + r: v.r * m, + x: v.x * m, + y: v.y * m, + z: v.z * m + } +} diff --git a/test/index.test.js b/test/index.test.js index 9bd71fe3..2f5b9c44 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -20,6 +20,7 @@ const units = { 'affineplane.size2': require('./size2/index.test'), 'affineplane.vec2': require('./vec2/index.test'), 'affineplane.vec3': require('./vec3/index.test'), + 'affineplane.vec4': require('./vec4/index.test'), 'affineplane.version': require('./version/index.test') } diff --git a/test/vec4/add.test.js b/test/vec4/add.test.js new file mode 100644 index 00000000..7928e312 --- /dev/null +++ b/test/vec4/add.test.js @@ -0,0 +1,26 @@ +const affineplane = require('../../index') +const vec4 = affineplane.vec4 + +module.exports = (ts) => { + ts.test('case: zeroes and ones', (t) => { + t.deepEqual( + vec4.add( + { r: 0, x: 0, y: 0, z: 0 }, + { r: 0, x: 0, y: 0, z: 0 } + ), + { r: 0, x: 0, y: 0, z: 0 }, + 'zeroes' + ) + + t.deepEqual( + vec4.add( + { r: 1, x: 1, y: 1, z: 1 }, + { r: 1, x: 1, y: 1, z: 1 } + ), + { r: 2, x: 2, y: 2, z: 2 }, + 'ones' + ) + + t.end() + }) +} diff --git a/test/vec4/hamilton.test.js b/test/vec4/hamilton.test.js new file mode 100644 index 00000000..476d2c8a --- /dev/null +++ b/test/vec4/hamilton.test.js @@ -0,0 +1,18 @@ +const affineplane = require('../../index') +const vec4 = affineplane.vec4 +const SQ2 = Math.sqrt(2) + +module.exports = (ts) => { + ts.test('case: basic hamilton product', (t) => { + t.deepEqual( + vec4.hamilton( + { r: 0, x: SQ2, y: SQ2, z: 0 }, + { r: 0, x: 0, y: 0, z: 2 } + ), + { r: 0, x: 2 * SQ2, y: -2 * SQ2, z: 0 }, + 'hamilton of two 3D vectors' + ) + + t.end() + }) +} diff --git a/test/vec4/index.test.js b/test/vec4/index.test.js new file mode 100644 index 00000000..d50c42d3 --- /dev/null +++ b/test/vec4/index.test.js @@ -0,0 +1,11 @@ +// A unit for each method. +const units = { + add: require('./add.test'), + hamilton: require('./hamilton.test') +} + +module.exports = (t) => { + Object.keys(units).forEach((unitName) => { + t.test('affineplane.vec4.' + unitName, units[unitName]) + }) +} From bb8f6bf3a5724e71419bb12f6de618f74d437267 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 12 Oct 2022 16:10:24 +0300 Subject: [PATCH 03/41] lint vec4 index --- lib/vec4/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/vec4/index.js b/lib/vec4/index.js index 001749d8..e43c8fd4 100644 --- a/lib/vec4/index.js +++ b/lib/vec4/index.js @@ -10,5 +10,4 @@ exports.add = require('./add') exports.hamilton = require('./hamilton') exports.create = require('./create') exports.norm = require('./norm') -exports.product = exports.combine exports.scaleBy = require('./scaleBy') From 4745b6a67dcc4776042971a2ccc8c042c2dd06bf Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 12 Oct 2022 16:26:42 +0300 Subject: [PATCH 04/41] impl point3.round --- lib/point3/index.js | 1 + lib/point3/round.js | 20 ++++++++++++++++++++ test/point3/index.test.js | 1 + test/point3/round.test.js | 19 +++++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 lib/point3/round.js create mode 100644 test/point3/round.test.js diff --git a/lib/point3/index.js b/lib/point3/index.js index d552a4c6..151df4cb 100644 --- a/lib/point3/index.js +++ b/lib/point3/index.js @@ -23,6 +23,7 @@ exports.offset = require('./offset') exports.polarOffset = require('./polarOffset') exports.projectTo = require('./projectTo') exports.rotateBy = require('./rotateBy') +exports.round = require('./round') exports.toArray = require('./toArray') exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') diff --git a/lib/point3/round.js b/lib/point3/round.js new file mode 100644 index 00000000..d79ed6f4 --- /dev/null +++ b/lib/point3/round.js @@ -0,0 +1,20 @@ +const round = Math.round + +module.exports = (p) => { + // affineplane.point3.round(p) + // + // Move point to the closest integer coordinate. + // + // Parameters: + // p + // a point3 + // + // Return + // a point3, having integer coordinates. + // + return { + x: round(p.x), + y: round(p.y), + z: round(p.z) + } +} diff --git a/test/point3/index.test.js b/test/point3/index.test.js index d471267b..45a677ac 100644 --- a/test/point3/index.test.js +++ b/test/point3/index.test.js @@ -12,6 +12,7 @@ const units = { polarOffset: require('./polarOffset.test'), projectTo: require('./projectTo.test'), rotateBy: require('./rotateBy.test'), + round: require('./round.test'), toArray: require('./toArray.test'), transitFrom: require('./transitFrom.test'), transitTo: require('./transitTo.test'), diff --git a/test/point3/round.test.js b/test/point3/round.test.js new file mode 100644 index 00000000..b4123abe --- /dev/null +++ b/test/point3/round.test.js @@ -0,0 +1,19 @@ +const point3 = require('../../lib/point3') + +module.exports = (ts) => { + ts.test('case: basic round', (t) => { + t.deepEqual( + point3.round({ x: 0, y: 1, z: 2 }), + { x: 0, y: 1, z: 2 }, + 'already integer' + ) + + t.deepEqual( + point3.round({ x: 0.2, y: 1.4, z: 1.51 }), + { x: 0, y: 1, z: 2 }, + 'should round to nearest integer' + ) + + t.end() + }) +} From b78465b0ac298e3223db4f5c18bba419cabc6d65 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 12 Oct 2022 16:28:47 +0300 Subject: [PATCH 05/41] add missing helm3.transitTo doc --- lib/helm3/transitTo.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/helm3/transitTo.js b/lib/helm3/transitTo.js index 44367329..c9fb1ffe 100644 --- a/lib/helm3/transitTo.js +++ b/lib/helm3/transitTo.js @@ -2,6 +2,8 @@ const inverse = require('../helm3/inverse') const transitFrom = require('./transitFrom') module.exports = (tr, target) => { + // affineplane.helm3.transitTo(tr, target) + // // Transit a helm3 to a target plane. // In other words, represent the helm3 // in the coordinate system of the plane. From 6054ef18337464d7b255681dba6d8b2816110f35 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 12 Oct 2022 16:36:27 +0300 Subject: [PATCH 06/41] alias helm3.translateBy .addTranslation --- lib/helm3/addTranslation.js | 1 + lib/helm3/index.js | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/helm3/addTranslation.js b/lib/helm3/addTranslation.js index 7cf392ea..65f49c5a 100644 --- a/lib/helm3/addTranslation.js +++ b/lib/helm3/addTranslation.js @@ -1,5 +1,6 @@ module.exports = function (tr, vec) { // affineplane.helm3.addTranslation(tr, vec) + // affineplane.helm3.translateBy // // Modify transformation so that its image // is translated by the given vector. diff --git a/lib/helm3/index.js b/lib/helm3/index.js index a80821f8..4209dc7d 100644 --- a/lib/helm3/index.js +++ b/lib/helm3/index.js @@ -119,4 +119,5 @@ exports.toArray = require('./toArray') exports.toMatrix = require('./toMatrix') exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') +exports.translateBy = exports.addTranslation exports.validate = require('./validate') From 1d64187090013e74766e6be2af5c22ef3200db47 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Sat, 15 Oct 2022 15:40:16 +0300 Subject: [PATCH 07/41] add link to interactive quaternion videos --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e31bd6f1..700c3fef 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A functional 2D plane geometry and dynamics library for spatial 2D and 3D applications. Designed especially for scaleable and rotatable [affine geometry](https://en.wikipedia.org/wiki/Affine_space) where parallel 2D planes float in 3D space and undergo [perspective projections](https://en.wikipedia.org/wiki/3D_projection) and [basis changes](https://en.wikipedia.org/wiki/Change_of_basis). Written in JavaScript (ECMAScript 6) with a functional, immutable, class-free style. -[Install](#install) - [Geometry](#geometry) - [Usage](#using-data-structures-and-functions) - [API Docs](https://axelpale.github.io/affineplane/docs/API.html) - [Contribute](#contribute) - [Github](https://github.com/axelpale/affineplane) +[Install](#install) - [Geometry](#geometry) - [Usage](#using-data-structures-and-functions) - [API Docs](https://axelpale.github.io/affineplane/docs/API.html) - [Contribute](#contribute) - [See also](#see-also) - [Github](https://github.com/axelpale/affineplane) ![affineplane social banner](docs/affineplane-social-banner.jpg) @@ -153,6 +153,8 @@ List of [2D Geometry Libraries for JavaScript](https://www.akselipalen.com/2021/ Interactive transformation matrix visualizers are useful for learning. For example, see [2D Matrix Visualizer](https://web.ma.utexas.edu/users/ysulyma/matrix/) ([alt](https://lmqm.xyz/a/fxh/matrix_visualizer)) by [Yuri Sulyma](https://ysulyma.github.io/) and [Simple 3D Matrix Visualizer](https://harry7557558.github.io/tools/matrixv.html) by [Harry Chen](https://harry7557558.github.io/). +To wrap your head around 3D rotations and quaternions, see [interactive quaternion rotation videos by Sanderson and Eater](https://eater.net/quaternions/video/intro) featured on [3blue1brown YouTube channel](https://www.youtube.com/watch?v=zjMuIxRvygQ). + ## License [MIT](LICENSE) From 0ea5e2a4a137104ab013fb4ea8ad379bdd670299 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 19 Oct 2022 22:24:39 +0300 Subject: [PATCH 08/41] use common x,y,z,w for 4d vectors --- lib/vec4/add.js | 6 +++--- lib/vec4/create.js | 10 +++++----- lib/vec4/index.js | 5 +---- lib/vec4/norm.js | 2 +- lib/vec4/scaleBy.js | 4 ++-- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/vec4/add.js b/lib/vec4/add.js index 72f6b18d..9e25453d 100644 --- a/lib/vec4/add.js +++ b/lib/vec4/add.js @@ -1,7 +1,7 @@ module.exports = (v, w) => { // affineplane.vec4.add(v, w) // - // Add two quaternions together via vector addition. + // Add two vectors together via component-wise addition. // // Parameters: // v @@ -13,9 +13,9 @@ module.exports = (v, w) => { // a vec4 // return { - r: v.r + w.r, x: v.x + w.x, y: v.y + w.y, - z: v.z + w.z + z: v.z + w.z, + w: v.w + w.w } } diff --git a/lib/vec4/create.js b/lib/vec4/create.js index 11cebc15..fbfb77de 100644 --- a/lib/vec4/create.js +++ b/lib/vec4/create.js @@ -1,18 +1,18 @@ -module.exports = (r, x, y, z) => { - // affineplane.vec4.create(r, x, y, z) +module.exports = (x, y, z, w) => { + // affineplane.vec4.create(x, y, z, w) // // Parameters: - // r - // a number // x // a number // y // a number // z // a number + // w + // a number // // Return // a vec4 // - return { r, x, y, z } + return { x, y, z, w } } diff --git a/lib/vec4/index.js b/lib/vec4/index.js index e43c8fd4..a40ff364 100644 --- a/lib/vec4/index.js +++ b/lib/vec4/index.js @@ -1,9 +1,6 @@ // affineplane.vec4 -// affineplane.quat // -// A vec4 is a 4D vector { r, x, y, z }. It can represent a quaternion -// and a rotating vector in 3D space, r being the radius or rotation at -// angle of 1 rad. +// A vec4 is a 4D vector { x, y, z, w }. // exports.add = require('./add') diff --git a/lib/vec4/norm.js b/lib/vec4/norm.js index 39fde2ce..a602100a 100644 --- a/lib/vec4/norm.js +++ b/lib/vec4/norm.js @@ -10,5 +10,5 @@ module.exports = (v) => { // Return // a number // - return Math.sqrt(v.r * v.r + v.x * v.x + v.y * v.y + v.z * v.z) + return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w) } diff --git a/lib/vec4/scaleBy.js b/lib/vec4/scaleBy.js index 311b6d00..f5a81c76 100644 --- a/lib/vec4/scaleBy.js +++ b/lib/vec4/scaleBy.js @@ -13,9 +13,9 @@ module.exports = (v, m) => { // a vec4 // return { - r: v.r * m, x: v.x * m, y: v.y * m, - z: v.z * m + z: v.z * m, + w: v.w * m } } From 062a8c3bfe4f485768f38313f6323a27f30fb884 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 19 Oct 2022 22:42:24 +0300 Subject: [PATCH 09/41] clean up vec4 --- lib/index.js | 1 - lib/vec4/hamilton.js | 21 --------------------- lib/vec4/index.js | 1 - test/vec4/add.test.js | 12 ++++++------ test/vec4/hamilton.test.js | 18 ------------------ test/vec4/index.test.js | 3 +-- 6 files changed, 7 insertions(+), 49 deletions(-) delete mode 100644 lib/vec4/hamilton.js delete mode 100644 test/vec4/hamilton.test.js diff --git a/lib/index.js b/lib/index.js index 87046d0e..f8bd3fa0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -60,7 +60,6 @@ exports.vec3 = require('./vec3') exports.vector3 = exports.vec3 exports.vec4 = require('./vec4') -exports.quat = exports.vec4 // affineplane.version // diff --git a/lib/vec4/hamilton.js b/lib/vec4/hamilton.js deleted file mode 100644 index bd368715..00000000 --- a/lib/vec4/hamilton.js +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = (v, w) => { - // affineplane.vec4.hamilton(v, w) - // - // The Hamilton product of two quaternions. - // - // Parameters - // v - // a vec4 - // w - // a vec4 - // - // Return - // a vec4 - // - return { - r: v.r * w.r - v.x * w.x - v.y * w.y - v.z * w.z, - x: v.r * w.x + v.x * w.r + v.y * w.z - v.z * w.y, - y: v.r * w.y - v.x * w.z + v.y * w.r + v.z * w.x, - z: v.r * w.z + v.x * w.y - v.y * w.x + v.z * w.r - } -} diff --git a/lib/vec4/index.js b/lib/vec4/index.js index a40ff364..4a013745 100644 --- a/lib/vec4/index.js +++ b/lib/vec4/index.js @@ -4,7 +4,6 @@ // exports.add = require('./add') -exports.hamilton = require('./hamilton') exports.create = require('./create') exports.norm = require('./norm') exports.scaleBy = require('./scaleBy') diff --git a/test/vec4/add.test.js b/test/vec4/add.test.js index 7928e312..58a61cf8 100644 --- a/test/vec4/add.test.js +++ b/test/vec4/add.test.js @@ -5,19 +5,19 @@ module.exports = (ts) => { ts.test('case: zeroes and ones', (t) => { t.deepEqual( vec4.add( - { r: 0, x: 0, y: 0, z: 0 }, - { r: 0, x: 0, y: 0, z: 0 } + { x: 0, y: 0, z: 0, w: 0 }, + { x: 0, y: 0, z: 0, w: 0 } ), - { r: 0, x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: 0, w: 0 }, 'zeroes' ) t.deepEqual( vec4.add( - { r: 1, x: 1, y: 1, z: 1 }, - { r: 1, x: 1, y: 1, z: 1 } + { x: 1, y: 1, z: 1, w: 1 }, + { x: 1, y: 1, z: 1, w: 1 } ), - { r: 2, x: 2, y: 2, z: 2 }, + { x: 2, y: 2, z: 2, w: 2 }, 'ones' ) diff --git a/test/vec4/hamilton.test.js b/test/vec4/hamilton.test.js deleted file mode 100644 index 476d2c8a..00000000 --- a/test/vec4/hamilton.test.js +++ /dev/null @@ -1,18 +0,0 @@ -const affineplane = require('../../index') -const vec4 = affineplane.vec4 -const SQ2 = Math.sqrt(2) - -module.exports = (ts) => { - ts.test('case: basic hamilton product', (t) => { - t.deepEqual( - vec4.hamilton( - { r: 0, x: SQ2, y: SQ2, z: 0 }, - { r: 0, x: 0, y: 0, z: 2 } - ), - { r: 0, x: 2 * SQ2, y: -2 * SQ2, z: 0 }, - 'hamilton of two 3D vectors' - ) - - t.end() - }) -} diff --git a/test/vec4/index.test.js b/test/vec4/index.test.js index d50c42d3..97b135fb 100644 --- a/test/vec4/index.test.js +++ b/test/vec4/index.test.js @@ -1,7 +1,6 @@ // A unit for each method. const units = { - add: require('./add.test'), - hamilton: require('./hamilton.test') + add: require('./add.test') } module.exports = (t) => { From 48d3b1b9c85345ee15b690d744011d74fe602e81 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 19 Oct 2022 22:47:19 +0300 Subject: [PATCH 10/41] init quat4 --- lib/index.js | 2 ++ lib/quat4/add.js | 21 +++++++++++++++++++++ lib/quat4/create.js | 18 ++++++++++++++++++ lib/quat4/hamilton.js | 22 ++++++++++++++++++++++ lib/quat4/index.js | 10 ++++++++++ lib/quat4/norm.js | 14 ++++++++++++++ test/index.test.js | 1 + test/quat4/hamilton.test.js | 18 ++++++++++++++++++ test/quat4/index.test.js | 10 ++++++++++ 9 files changed, 116 insertions(+) create mode 100644 lib/quat4/add.js create mode 100644 lib/quat4/create.js create mode 100644 lib/quat4/hamilton.js create mode 100644 lib/quat4/index.js create mode 100644 lib/quat4/norm.js create mode 100644 test/quat4/hamilton.test.js create mode 100644 test/quat4/index.test.js diff --git a/lib/index.js b/lib/index.js index f8bd3fa0..9621ffe3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -30,6 +30,8 @@ exports.line3 = require('./line3') // TODO orient2 // TODO orient3 +exports.quat4 = require('./quat4') + exports.path2 = require('./path2') exports.poly2 = require('./poly2') diff --git a/lib/quat4/add.js b/lib/quat4/add.js new file mode 100644 index 00000000..06fcc3a7 --- /dev/null +++ b/lib/quat4/add.js @@ -0,0 +1,21 @@ +module.exports = (q, p) => { + // affineplane.quat4.add(q, p) + // + // Add two quaternions together via component-wise addition. + // + // Parameters: + // q + // a quat4 + // p + // a quat4 + // + // Return + // a quat4 + // + return { + a: q.a + p.a, + b: q.b + p.b, + c: q.c + p.c, + d: q.d + p.d + } +} diff --git a/lib/quat4/create.js b/lib/quat4/create.js new file mode 100644 index 00000000..f4aeac40 --- /dev/null +++ b/lib/quat4/create.js @@ -0,0 +1,18 @@ +module.exports = (a, b, c, d) => { + // affineplane.quat4.create(a, b, c, d) + // + // Parameters: + // a + // a number + // b + // a number + // c + // a number + // d + // a number + // + // Return + // a quat4 + // + return { a, b, c, d } +} diff --git a/lib/quat4/hamilton.js b/lib/quat4/hamilton.js new file mode 100644 index 00000000..157315cd --- /dev/null +++ b/lib/quat4/hamilton.js @@ -0,0 +1,22 @@ +module.exports = (q, p) => { + // affineplane.quat4.hamilton(q, p) + // affineplane.quat4.multiply + // + // The Hamilton product of two quaternions. + // + // Parameters + // q + // a quat4 + // p + // a quat4 + // + // Return + // a quat4 + // + return { + a: q.a * p.a - q.b * p.b - q.c * p.c - q.d * p.d, + b: q.a * p.b + q.b * p.a + q.c * p.d - q.d * p.c, + c: q.a * p.c - q.b * p.d + q.c * p.a + q.d * p.b, + d: q.a * p.d + q.b * p.c - q.c * p.b + q.d * p.a + } +} diff --git a/lib/quat4/index.js b/lib/quat4/index.js new file mode 100644 index 00000000..692e0227 --- /dev/null +++ b/lib/quat4/index.js @@ -0,0 +1,10 @@ +// affineplane.quat4 +// +// A quaternion { a, b, c, d } +// + +exports.add = require('./add') +exports.create = require('./create') +exports.hamilton = require('./hamilton') +exports.multiply = exports.hamilton +exports.norm = require('./norm') diff --git a/lib/quat4/norm.js b/lib/quat4/norm.js new file mode 100644 index 00000000..760b61d5 --- /dev/null +++ b/lib/quat4/norm.js @@ -0,0 +1,14 @@ +module.exports = (q) => { + // affineplane.quat4.norm(q) + // + // The length of quaternion. Also called the norm. + // + // Parameters: + // q + // a quat4 + // + // Return + // a number + // + return Math.sqrt(q.a * q.a + q.b * q.b + q.c * q.c + q.d * q.d) +} diff --git a/test/index.test.js b/test/index.test.js index 2f5b9c44..2b7a29d7 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -13,6 +13,7 @@ const units = { 'affineplane.helm3': require('./helm3/index.test'), 'affineplane.line2': require('./line2/index.test'), 'affineplane.line3': require('./line3/index.test'), + 'affineplane.quat4': require('./quat4/index.test'), 'affineplane.plane2': require('./plane2/index.test'), 'affineplane.plane3': require('./plane3/index.test'), 'affineplane.point2': require('./point2/index.test'), diff --git a/test/quat4/hamilton.test.js b/test/quat4/hamilton.test.js new file mode 100644 index 00000000..120c28a5 --- /dev/null +++ b/test/quat4/hamilton.test.js @@ -0,0 +1,18 @@ +const affineplane = require('../../index') +const quat4 = affineplane.quat4 +const SQ2 = Math.sqrt(2) + +module.exports = (ts) => { + ts.test('case: basic hamilton product', (t) => { + t.deepEqual( + quat4.hamilton( + { a: 0, b: SQ2, c: SQ2, d: 0 }, + { a: 0, b: 0, c: 0, d: 2 } + ), + { a: 0, b: 2 * SQ2, c: -2 * SQ2, d: 0 }, + 'hamilton of two 3D vectors' + ) + + t.end() + }) +} diff --git a/test/quat4/index.test.js b/test/quat4/index.test.js new file mode 100644 index 00000000..fc5c647b --- /dev/null +++ b/test/quat4/index.test.js @@ -0,0 +1,10 @@ +// A unit for each method. +const units = { + hamilton: require('./hamilton.test') +} + +module.exports = (t) => { + Object.keys(units).forEach((unitName) => { + t.test('affineplane.quat4.' + unitName, units[unitName]) + }) +} From 796afcc4aec1fa237a402ca57994e63f5fbe7c11 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 20 Oct 2022 15:54:58 +0300 Subject: [PATCH 11/41] impl quat4.conjugate --- lib/quat4/conjugate.js | 19 +++++++++++++++++++ lib/quat4/index.js | 1 + test/quat4/conjugate.test.js | 14 ++++++++++++++ test/quat4/index.test.js | 1 + 4 files changed, 35 insertions(+) create mode 100644 lib/quat4/conjugate.js create mode 100644 test/quat4/conjugate.test.js diff --git a/lib/quat4/conjugate.js b/lib/quat4/conjugate.js new file mode 100644 index 00000000..4de69243 --- /dev/null +++ b/lib/quat4/conjugate.js @@ -0,0 +1,19 @@ +module.exports = (q) => { + // affineplane.quat4.conjugate(q) + // + // Get the conjugate of a quaternion. + // + // Parameters: + // q + // a quat4 + // + // Return + // a quat4 + // + return { + a: q.a, + b: -q.b, + c: -q.c, + d: -q.d + } +} diff --git a/lib/quat4/index.js b/lib/quat4/index.js index 692e0227..c3e368ac 100644 --- a/lib/quat4/index.js +++ b/lib/quat4/index.js @@ -4,6 +4,7 @@ // exports.add = require('./add') +exports.conjugate = require('./conjugate') exports.create = require('./create') exports.hamilton = require('./hamilton') exports.multiply = exports.hamilton diff --git a/test/quat4/conjugate.test.js b/test/quat4/conjugate.test.js new file mode 100644 index 00000000..d16d1b93 --- /dev/null +++ b/test/quat4/conjugate.test.js @@ -0,0 +1,14 @@ +const affineplane = require('../../index') +const quat4 = affineplane.quat4 + +module.exports = (ts) => { + ts.test('case: basic conjugate', (t) => { + t.deepEqual( + quat4.conjugate({ a: 1, b: 2, c: -3, d: 4 }), + { a: 1, b: -2, c: 3, d: -4 }, + 'vector part should be negated' + ) + + t.end() + }) +} diff --git a/test/quat4/index.test.js b/test/quat4/index.test.js index fc5c647b..14398c6f 100644 --- a/test/quat4/index.test.js +++ b/test/quat4/index.test.js @@ -1,5 +1,6 @@ // A unit for each method. const units = { + conjugate: require('./conjugate.test'), hamilton: require('./hamilton.test') } From 54a1d0be65a6fb7eadd682452c0d50acd8a751ea Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 20 Oct 2022 16:03:52 +0300 Subject: [PATCH 12/41] impl quat4.diff --- lib/quat4/difference.js | 22 ++++++++++++++++++++++ lib/quat4/index.js | 2 ++ test/quat4/difference.test.js | 17 +++++++++++++++++ test/quat4/index.test.js | 1 + 4 files changed, 42 insertions(+) create mode 100644 lib/quat4/difference.js create mode 100644 test/quat4/difference.test.js diff --git a/lib/quat4/difference.js b/lib/quat4/difference.js new file mode 100644 index 00000000..b755e26a --- /dev/null +++ b/lib/quat4/difference.js @@ -0,0 +1,22 @@ +module.exports = (q, p) => { + // affineplane.quat4.difference(q, p) + // affineplane.quat4.diff + // + // Get the quaternion q - p. + // + // Parameters: + // q + // a quat4 + // p + // a quat4 + // + // Return + // a quat4 + // + return { + a: p.a - q.a, + b: p.b - q.b, + c: p.c - q.c, + d: p.d - q.d + } +} diff --git a/lib/quat4/index.js b/lib/quat4/index.js index c3e368ac..1b627fac 100644 --- a/lib/quat4/index.js +++ b/lib/quat4/index.js @@ -6,6 +6,8 @@ exports.add = require('./add') exports.conjugate = require('./conjugate') exports.create = require('./create') +exports.difference = require('./difference') +exports.diff = exports.difference exports.hamilton = require('./hamilton') exports.multiply = exports.hamilton exports.norm = require('./norm') diff --git a/test/quat4/difference.test.js b/test/quat4/difference.test.js new file mode 100644 index 00000000..738f826e --- /dev/null +++ b/test/quat4/difference.test.js @@ -0,0 +1,17 @@ +const affineplane = require('../../index') +const quat4 = affineplane.quat4 + +module.exports = (ts) => { + ts.test('case: basic diff', (t) => { + t.deepEqual( + quat4.diff( + { a: 1, b: 2, c: 3, d: 4 }, + { a: 1, b: 2, c: 3, d: 4 } + ), + { a: 0, b: 0, c: 0, d: 0 }, + 'should be zero quaternion' + ) + + t.end() + }) +} diff --git a/test/quat4/index.test.js b/test/quat4/index.test.js index 14398c6f..8c4883b8 100644 --- a/test/quat4/index.test.js +++ b/test/quat4/index.test.js @@ -1,6 +1,7 @@ // A unit for each method. const units = { conjugate: require('./conjugate.test'), + difference: require('./difference.test'), hamilton: require('./hamilton.test') } From 830e4ede80b369d598027329688626c97e6d6cc2 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 20 Oct 2022 17:12:59 +0300 Subject: [PATCH 13/41] impl quat4.fromVector --- lib/quat4/fromVector.js | 19 +++++++++++++++++++ lib/quat4/index.js | 1 + test/quat4/fromVector.test.js | 14 ++++++++++++++ test/quat4/index.test.js | 1 + 4 files changed, 35 insertions(+) create mode 100644 lib/quat4/fromVector.js create mode 100644 test/quat4/fromVector.test.js diff --git a/lib/quat4/fromVector.js b/lib/quat4/fromVector.js new file mode 100644 index 00000000..15fe00ff --- /dev/null +++ b/lib/quat4/fromVector.js @@ -0,0 +1,19 @@ +module.exports = (vec) => { + // affineplane.quat4.fromVector(vec) + // + // Construct a vector quaternion i.e. a quaternion with zero scalar part. + // + // Parameters: + // vec + // a vec3 + // + // Return + // a quat4 + // + return { + a: 0, + b: vec.x, + c: vec.y, + d: vec.z + } +} diff --git a/lib/quat4/index.js b/lib/quat4/index.js index 1b627fac..bebf36f8 100644 --- a/lib/quat4/index.js +++ b/lib/quat4/index.js @@ -8,6 +8,7 @@ exports.conjugate = require('./conjugate') exports.create = require('./create') exports.difference = require('./difference') exports.diff = exports.difference +exports.fromVector = require('./fromVector') exports.hamilton = require('./hamilton') exports.multiply = exports.hamilton exports.norm = require('./norm') diff --git a/test/quat4/fromVector.test.js b/test/quat4/fromVector.test.js new file mode 100644 index 00000000..d3be2268 --- /dev/null +++ b/test/quat4/fromVector.test.js @@ -0,0 +1,14 @@ +const affineplane = require('../../index') +const quat4 = affineplane.quat4 + +module.exports = (ts) => { + ts.test('case: basic fromVector', (t) => { + t.deepEqual( + quat4.fromVector({ x: 1, y: 2, z: -3 }), + { a: 0, b: 1, c: 2, d: -3 }, + 'scalar should be zero' + ) + + t.end() + }) +} diff --git a/test/quat4/index.test.js b/test/quat4/index.test.js index 8c4883b8..3e4e6f3e 100644 --- a/test/quat4/index.test.js +++ b/test/quat4/index.test.js @@ -2,6 +2,7 @@ const units = { conjugate: require('./conjugate.test'), difference: require('./difference.test'), + fromVector: require('./fromVector.test'), hamilton: require('./hamilton.test') } From 0fe5a771e6b07944d957e070b9cce3a174d12ee6 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 20 Oct 2022 17:17:39 +0300 Subject: [PATCH 14/41] test quat4.norm --- test/quat4/index.test.js | 3 ++- test/quat4/norm.test.js | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 test/quat4/norm.test.js diff --git a/test/quat4/index.test.js b/test/quat4/index.test.js index 3e4e6f3e..df0bca67 100644 --- a/test/quat4/index.test.js +++ b/test/quat4/index.test.js @@ -3,7 +3,8 @@ const units = { conjugate: require('./conjugate.test'), difference: require('./difference.test'), fromVector: require('./fromVector.test'), - hamilton: require('./hamilton.test') + hamilton: require('./hamilton.test'), + norm: require('./norm.test') } module.exports = (t) => { diff --git a/test/quat4/norm.test.js b/test/quat4/norm.test.js new file mode 100644 index 00000000..65b8afc7 --- /dev/null +++ b/test/quat4/norm.test.js @@ -0,0 +1,14 @@ +const affineplane = require('../../index') +const quat4 = affineplane.quat4 + +module.exports = (ts) => { + ts.test('case: basic norm', (t) => { + t.equal( + quat4.norm({ a: 1, b: 1, c: 1, d: 1 }), + 2, + 'should be square root of squared sum' + ) + + t.end() + }) +} From 60c1bba093bec31002598bb0a07475c767ffeee6 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 20 Oct 2022 17:22:35 +0300 Subject: [PATCH 15/41] test quat4.add --- test/quat4/add.test.js | 17 +++++++++++++++++ test/quat4/index.test.js | 1 + 2 files changed, 18 insertions(+) create mode 100644 test/quat4/add.test.js diff --git a/test/quat4/add.test.js b/test/quat4/add.test.js new file mode 100644 index 00000000..d27566e8 --- /dev/null +++ b/test/quat4/add.test.js @@ -0,0 +1,17 @@ +const affineplane = require('../../index') +const quat4 = affineplane.quat4 + +module.exports = (ts) => { + ts.test('case: basic add', (t) => { + t.deepEqual( + quat4.add( + { a: 1, b: 2, c: 3, d: 4 }, + { a: 1, b: 2, c: 3, d: 4 } + ), + { a: 2, b: 4, c: 6, d: 8 }, + 'should be double' + ) + + t.end() + }) +} diff --git a/test/quat4/index.test.js b/test/quat4/index.test.js index df0bca67..7a9c02af 100644 --- a/test/quat4/index.test.js +++ b/test/quat4/index.test.js @@ -1,5 +1,6 @@ // A unit for each method. const units = { + add: require('./add.test'), conjugate: require('./conjugate.test'), difference: require('./difference.test'), fromVector: require('./fromVector.test'), From 8e4f39e443ff8b9b82782db1d55a5ea3a7ec5458 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 20 Oct 2022 18:06:28 +0300 Subject: [PATCH 16/41] doc vec3.norm alias --- lib/vec3/magnitude.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/vec3/magnitude.js b/lib/vec3/magnitude.js index c4987097..eb1cec8a 100644 --- a/lib/vec3/magnitude.js +++ b/lib/vec3/magnitude.js @@ -1,5 +1,6 @@ module.exports = (v) => { // affineplane.vec3.magnitude(v) + // affineplane.vec3.norm // // The euclidean length of the vector. // From 444302a0e36594c53a701f45430e6fa6c3841f0c Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 20 Oct 2022 18:46:11 +0300 Subject: [PATCH 17/41] impl vec3.rotateAroundAxis --- lib/vec3/index.js | 1 + lib/vec3/rotateAroundAxis.js | 48 ++++++++++++++++++++++++++++++ test/vec3/index.test.js | 1 + test/vec3/rotateAroundAxis.test.js | 27 +++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 lib/vec3/rotateAroundAxis.js create mode 100644 test/vec3/rotateAroundAxis.test.js diff --git a/lib/vec3/index.js b/lib/vec3/index.js index 411ad654..159ceac3 100644 --- a/lib/vec3/index.js +++ b/lib/vec3/index.js @@ -28,6 +28,7 @@ exports.negate = exports.invert exports.norm = exports.magnitude exports.normalize = require('./unit') exports.projectTo = require('./projectTo') +exports.rotateAroundAxis = require('./rotateAroundAxis') exports.rotateBy = require('./rotateBy') exports.rotateTo = require('./rotateTo') exports.scaleBy = require('./scaleBy') diff --git a/lib/vec3/rotateAroundAxis.js b/lib/vec3/rotateAroundAxis.js new file mode 100644 index 00000000..bd50d231 --- /dev/null +++ b/lib/vec3/rotateAroundAxis.js @@ -0,0 +1,48 @@ +const ham = require('../quat4/hamilton') +const norm = require('./magnitude') + +module.exports = function (v, axis, angle) { + // affineplane.vec3.rotateAroundAxis(v, axis, angle) + // + // Rotate a vector by the given radian angle around the specified axis vector. + // The method uses [Rodriques' rotation formula]( + // https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula) under + // the hood. If you need to rotate multiple vectors simultaneously, + // it is probably better to construct a rotation matrix first and apply + // it to each vector. + // + // Parameters + // v + // a vec3 + // axis + // a vec3, must not be zero vector + // angle + // a number, an angle in radians. + // .. Right-hand rotation around the given axis. + // + // Return + // a vec3 + // + + const co = Math.cos(angle) + const si = Math.sin(angle) + const ico = 1 - co + + const vx = v.x + const vy = v.y + const vz = v.z + + const kn = norm(axis) + const kx = axis.x / kn + const ky = axis.y / kn + const kz = axis.z / kn + + const dot = kx * vx + ky * vy + kz * vz + const dico = dot * ico + + return { + x: vx * co + (ky * vz - kz * vy) * si + kx * dico, + y: vy * co + (kz * vx - kx * vz) * si + ky * dico, + z: vz * co + (kx * vy - ky * vx) * si + kz * dico + } +} diff --git a/test/vec3/index.test.js b/test/vec3/index.test.js index cba65c0b..cb86e5dd 100644 --- a/test/vec3/index.test.js +++ b/test/vec3/index.test.js @@ -12,6 +12,7 @@ const units = { invert: require('./invert.test'), magnitude: require('./magnitude.test'), projectTo: require('./projectTo.test'), + rotateAroundAxis: require('./rotateAroundAxis.test'), rotateBy: require('./rotateBy.test'), rotateTo: require('./rotateTo.test'), scaleBy: require('./scaleBy.test'), diff --git a/test/vec3/rotateAroundAxis.test.js b/test/vec3/rotateAroundAxis.test.js new file mode 100644 index 00000000..1680d607 --- /dev/null +++ b/test/vec3/rotateAroundAxis.test.js @@ -0,0 +1,27 @@ +const vec3 = require('../../lib/vec3') +const PI = Math.PI + +module.exports = (ts) => { + ts.test('case: basic rotation around axis', (t) => { + const i = { x: 1, y: 0, z: 0 } + t.almostEqual( + vec3.rotateAroundAxis(i, i, PI), + i, + 'around itself, no change' + ) + + t.almostEqual( + vec3.rotateAroundAxis(i, { x: 1, y: 1, z: 1 }, 2 * PI / 3), + { x: 0, y: 1, z: 0 }, + 'around non-trivial axis' + ) + + t.almostEqual( + vec3.rotateAroundAxis(i, { x: -1, y: -1, z: -1 }, 4 * PI / 3), + { x: 0, y: 1, z: 0 }, + 'around negated axis' + ) + + t.end() + }) +} From 56ee6be43218347c195bf007fbf733b4f0e5f5be Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 20 Oct 2022 22:31:34 +0300 Subject: [PATCH 18/41] impl quat4 estimator but keep isolated until real use --- lib/quat4/estimator/estimate.js | 104 +++++++++++++++++++++++++++ lib/quat4/estimator/estimate.test.js | 27 +++++++ lib/quat4/estimator/mse.js | 45 ++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 lib/quat4/estimator/estimate.js create mode 100644 lib/quat4/estimator/estimate.test.js create mode 100644 lib/quat4/estimator/mse.js diff --git a/lib/quat4/estimator/estimate.js b/lib/quat4/estimator/estimate.js new file mode 100644 index 00000000..9b10be4b --- /dev/null +++ b/lib/quat4/estimator/estimate.js @@ -0,0 +1,104 @@ +const epsilon = require('../epsilon') + +module.exports = (domain, range) => { + // affineplane.quat4.estimate(domain, range) + // + // Estimate a quaternion that rotates the domain set of quaternions + // as close to the range set of quaternions as possible. Note that + // this cannot be used to estimate rotation between two sets of 3D vectors, + // because quaternions model orientation, not direction. + // + // Parameters: + // domain + // array of quat4, the source quaternions + // range + // array of quat4, the target quaternions + // + // Return + // a quat4, the rotation from the domain to the range. + // + + // If unequal sizes are given, drop the excess. + const n = Math.min(domain.length, range.length) + + // The solution consists of mostly dot products of row vectors. + let da2 = 0 + let db2 = 0 + let dc2 = 0 + let dd2 = 0 + + let dara = 0 + let darb = 0 + let darc = 0 + let dard = 0 + + let dbra = 0 + let dbrb = 0 + let dbrc = 0 + let dbrd = 0 + + let dcra = 0 + let dcrb = 0 + let dcrc = 0 + let dcrd = 0 + + let ddra = 0 + let ddrb = 0 + let ddrc = 0 + let ddrd = 0 + + for (let i = 0; i < n; i += 1) { + const da = domain[i].a + const db = domain[i].b + const dc = domain[i].c + const dd = domain[i].d + + da2 += da * da + db2 += db * db + dc2 += dc * dc + dd2 += dd * dd + + const ra = range[i].a + const rb = range[i].b + const rc = range[i].c + const rd = range[i].d + + dara += da * ra + darb += da * rb + darc += da * rc + dard += da * rd + + dbra += db * ra + dbrb += db * rb + dbrc += db * rc + dbrd += db * rd + + dcra += dc * ra + dcrb += dc * rb + dcrc += dc * rc + dcrd += dc * rd + + ddra += dd * ra + ddrb += dd * rb + ddrc += dd * rc + ddrd += dd * rd + } + + const det = da2 + db2 + dc2 + dd2 + + // TODO avoid unnecessary dot products before the determinant test. + if (det <= epsilon) { + // Domain just a point. Return identity. + return { a: 1, b: 0, c: 0, d: 0 } + } + + const m = 1 / det + console.log('det', det) + + return { + a: m * (dara + dbrb + dcrc + ddrd), + b: m * (darb - dbra + dcrd - ddrc), + c: m * (darc - dbrd - dcra + ddrb), + d: m * (dard + dbrc - dcrb - ddra) + } +} diff --git a/lib/quat4/estimator/estimate.test.js b/lib/quat4/estimator/estimate.test.js new file mode 100644 index 00000000..45b3b115 --- /dev/null +++ b/lib/quat4/estimator/estimate.test.js @@ -0,0 +1,27 @@ +const quat4 = require('../../lib/quat4') +const estimate = require('../../lib/quat4/estimate') +const getmse = require('../../lib/quat4/mse') + +const domain = [ + { a: 0, b: 1, c: 0, d: 0 }, + { a: 0, b: 0, c: 1, d: 0 }, + { a: 0, b: 0, c: 0, d: 1 } +] + +const range = [ + { a: 0, b: -0.138, c: -0.161, d: 0.977 }, + { a: 0, b: -0.977, c: -0.138, d: -0.161 }, + { a: 0, b: 0.161, c: -0.977, d: -0.138 }, +] + +const est = estimate(domain, range) +const len = Math.sqrt(est.a**2 + est.b**2 + est.c**2 + est.d**2) +const angle = Math.acos(est.a / len) * 180 / Math.PI +const mse = getmse(est, domain, range) + +console.log('estimate:', est) +console.log('length:', len) +console.log('angle deg:', angle) +console.log('mse:', mse) + +console.log('mapped:', domain.map(q => quat4.hamilton(est, q))) diff --git a/lib/quat4/estimator/mse.js b/lib/quat4/estimator/mse.js new file mode 100644 index 00000000..c90d7bca --- /dev/null +++ b/lib/quat4/estimator/mse.js @@ -0,0 +1,45 @@ +const hamilton = require('./hamilton') +const diff = require('./difference') +const norm = require('./norm') + +module.exports = (q, domain, range) => { + // Mean squared error between the domain and range after the domain + // is left-multiplied by the quaternion q. + // Can be used to measure estimation error of quat4.estimate. + // + // Mathematically, the result is equal |QD - R|^2 / n, where Q is + // 4x4 matrix representing the quaternion q, D is + // + // Parameters: + // q + // a quat4 + // domain + // array of quat4 + // range + // array of quat4 + // + // Return + // a number + // + + const n = Math.min(domain.length, range.length) + + if (n === 0) { + return 0 + } + + let errsum = 0 + + for (let i = 0; i < n; i += 1) { + const qd = domain[i] + const qr = range[i] + const qqd = hamilton(q, qd) + const qerr = diff(qqd, qr) + const nerr = norm(qerr) + // Note unnecessary sqrt within norm. Need to re-square. + errsum += nerr * nerr + } + + // Average over + return errsum / n +} From a2c372cb55a80bd23e19527aa1ba831b64edc2cb Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 20 Oct 2022 22:57:12 +0300 Subject: [PATCH 19/41] impl quat4.fromAxisAngle --- lib/quat4/fromAxisAngle.js | 34 ++++++++++++++++++++++++++++++++ lib/quat4/index.js | 1 + test/quat4/fromAxisAngle.test.js | 15 ++++++++++++++ test/quat4/index.test.js | 1 + 4 files changed, 51 insertions(+) create mode 100644 lib/quat4/fromAxisAngle.js create mode 100644 test/quat4/fromAxisAngle.test.js diff --git a/lib/quat4/fromAxisAngle.js b/lib/quat4/fromAxisAngle.js new file mode 100644 index 00000000..dbda4431 --- /dev/null +++ b/lib/quat4/fromAxisAngle.js @@ -0,0 +1,34 @@ +const toUnit = require('../vec3/unit') + +module.exports = (dir, angle) => { + // affineplane.quat4.fromDirectionAndAngle(dir, angle) + // + // Construct a unit quaternion from an axis vector and a rotation angle + // around that direction. Note that if this quaternion is later used to + // rotate a 3D vector, the rotation angle will be applied twice to map + // the vector back to 3D. + // + // Parameters: + // dir + // a dir3 or a vec3 + // angle + // a number, an angle in radians + // + // Return + // a quat4 + // + + // Precompute + const co = Math.cos(angle) + const si = Math.sin(angle) + + // Ensure unit vector + const u = toUnit(dir) + + return { + a: co, + b: u.x * si, + c: u.y * si, + d: u.z * si + } +} diff --git a/lib/quat4/index.js b/lib/quat4/index.js index bebf36f8..5f7662be 100644 --- a/lib/quat4/index.js +++ b/lib/quat4/index.js @@ -8,6 +8,7 @@ exports.conjugate = require('./conjugate') exports.create = require('./create') exports.difference = require('./difference') exports.diff = exports.difference +exports.fromAxisAngle = require('./fromAxisAngle') exports.fromVector = require('./fromVector') exports.hamilton = require('./hamilton') exports.multiply = exports.hamilton diff --git a/test/quat4/fromAxisAngle.test.js b/test/quat4/fromAxisAngle.test.js new file mode 100644 index 00000000..e003b869 --- /dev/null +++ b/test/quat4/fromAxisAngle.test.js @@ -0,0 +1,15 @@ +const affineplane = require('../../index') +const quat4 = affineplane.quat4 + +module.exports = (ts) => { + ts.test('case: basic fromAxisAngle', (t) => { + const q = quat4.fromAxisAngle({ x: 3, y: 4, z: 12 }, Math.PI / 2) + const p = { a: 0, b: 3 / 13, c: 4 / 13, d: 12 / 13 } + t.almostEqual(q.a, p.a, 'scalar should be zero') + t.almostEqual(q.b, p.b, 'correct q.b') + t.almostEqual(q.c, p.c, 'correct q.c') + t.almostEqual(q.d, p.d, 'correct q.d') + + t.end() + }) +} diff --git a/test/quat4/index.test.js b/test/quat4/index.test.js index 7a9c02af..8b6d14aa 100644 --- a/test/quat4/index.test.js +++ b/test/quat4/index.test.js @@ -3,6 +3,7 @@ const units = { add: require('./add.test'), conjugate: require('./conjugate.test'), difference: require('./difference.test'), + fromAxisAngle: require('./fromAxisAngle.test'), fromVector: require('./fromVector.test'), hamilton: require('./hamilton.test'), norm: require('./norm.test') From 6fe14709ffb33659aaaeacd7758f1459c1a932cb Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 26 Oct 2022 18:37:04 +0300 Subject: [PATCH 20/41] remove excess rect2 at functions --- lib/rect2/atBottomLeft.js | 5 ----- lib/rect2/atBottomMid.js | 5 ----- lib/rect2/atBottomRight.js | 5 ----- lib/rect2/atMid.js | 5 ----- lib/rect2/atMidLeft.js | 5 ----- lib/rect2/atMidRight.js | 5 ----- lib/rect2/atTopLeft.js | 5 ----- lib/rect2/atTopMid.js | 5 ----- lib/rect2/atTopRight.js | 5 ----- lib/rect2/changeBasis.js | 37 ------------------------------------- lib/rect2/index.js | 10 ---------- 11 files changed, 92 deletions(-) delete mode 100644 lib/rect2/atBottomLeft.js delete mode 100644 lib/rect2/atBottomMid.js delete mode 100644 lib/rect2/atBottomRight.js delete mode 100644 lib/rect2/atMid.js delete mode 100644 lib/rect2/atMidLeft.js delete mode 100644 lib/rect2/atMidRight.js delete mode 100644 lib/rect2/atTopLeft.js delete mode 100644 lib/rect2/atTopMid.js delete mode 100644 lib/rect2/atTopRight.js delete mode 100644 lib/rect2/changeBasis.js diff --git a/lib/rect2/atBottomLeft.js b/lib/rect2/atBottomLeft.js deleted file mode 100644 index e655fa26..00000000 --- a/lib/rect2/atBottomLeft.js +++ /dev/null @@ -1,5 +0,0 @@ -const at = require('./at') - -module.exports = (rect) => { - return at(rect, 0, rect.h) -} diff --git a/lib/rect2/atBottomMid.js b/lib/rect2/atBottomMid.js deleted file mode 100644 index 94e68da3..00000000 --- a/lib/rect2/atBottomMid.js +++ /dev/null @@ -1,5 +0,0 @@ -const at = require('./at') - -module.exports = (rect) => { - return at(rect, rect.w / 2, rect.h) -} diff --git a/lib/rect2/atBottomRight.js b/lib/rect2/atBottomRight.js deleted file mode 100644 index 9e18bcd2..00000000 --- a/lib/rect2/atBottomRight.js +++ /dev/null @@ -1,5 +0,0 @@ -const at = require('./at') - -module.exports = (rect) => { - return at(rect, rect.w, rect.h) -} diff --git a/lib/rect2/atMid.js b/lib/rect2/atMid.js deleted file mode 100644 index 02e4c55c..00000000 --- a/lib/rect2/atMid.js +++ /dev/null @@ -1,5 +0,0 @@ -const at = require('./at') - -module.exports = (rect) => { - return at(rect, rect.w / 2, rect.h / 2) -} diff --git a/lib/rect2/atMidLeft.js b/lib/rect2/atMidLeft.js deleted file mode 100644 index b0c4f2c3..00000000 --- a/lib/rect2/atMidLeft.js +++ /dev/null @@ -1,5 +0,0 @@ -const at = require('./at') - -module.exports = (rect) => { - return at(rect, 0, rect.h / 2) -} diff --git a/lib/rect2/atMidRight.js b/lib/rect2/atMidRight.js deleted file mode 100644 index cdc1db3b..00000000 --- a/lib/rect2/atMidRight.js +++ /dev/null @@ -1,5 +0,0 @@ -const at = require('./at') - -module.exports = (rect) => { - return at(rect, rect.w, rect.h / 2) -} diff --git a/lib/rect2/atTopLeft.js b/lib/rect2/atTopLeft.js deleted file mode 100644 index a353917f..00000000 --- a/lib/rect2/atTopLeft.js +++ /dev/null @@ -1,5 +0,0 @@ -const at = require('./at') - -module.exports = (rect) => { - return at(rect, 0, 0) -} diff --git a/lib/rect2/atTopMid.js b/lib/rect2/atTopMid.js deleted file mode 100644 index a75bc665..00000000 --- a/lib/rect2/atTopMid.js +++ /dev/null @@ -1,5 +0,0 @@ -const at = require('./at') - -module.exports = (rect) => { - return at(rect, rect.w / 2, 0) -} diff --git a/lib/rect2/atTopRight.js b/lib/rect2/atTopRight.js deleted file mode 100644 index 9efc3664..00000000 --- a/lib/rect2/atTopRight.js +++ /dev/null @@ -1,5 +0,0 @@ -const at = require('./at') - -module.exports = (rect) => { - return at(rect, rect.w, 0) -} diff --git a/lib/rect2/changeBasis.js b/lib/rect2/changeBasis.js deleted file mode 100644 index 559894bd..00000000 --- a/lib/rect2/changeBasis.js +++ /dev/null @@ -1,37 +0,0 @@ -const inverse = require('../helm2/inverse') -const multiply = require('../helm2/compose') - -/// Why abxywh? Why not just xywh? -/// We cannot change the basis of a xywh rectangle freely. -/// We cannot represent the rectangle in all bases, because -/// the rectangle cannot rotate. Scaling of w h would be possible thou. -/// - -module.exports = (rect, sourceBasis, targetBasis) => { - // Convert a rectangle between bases. - // - // Parameters - // rect - // a rectangle on the source basis. - // sourceBasis - // a proj2, a transition from the source basis to - // ..the reference basis. - // targetBasis - // a proj2, a transition from the target basis to - // ..the reference basis. - // - // Return - // the rectangle on the target basis. - // - const inv = inverse(targetBasis) - const rel = multiply(inv, sourceBasis) - const form = multiply(rel, rect) - return { - a: form.a, - b: form.b, - x: form.x, - y: form.y, - w: rect.w, - h: rect.h - } -} diff --git a/lib/rect2/index.js b/lib/rect2/index.js index e74d26aa..20a6914e 100644 --- a/lib/rect2/index.js +++ b/lib/rect2/index.js @@ -1,13 +1,3 @@ exports.at = require('./at') -exports.atBottomLeft = require('./atBottomLeft') -exports.atBottomMid = require('./atBottomMid') -exports.atBottomRight = require('./atBottomRight') -exports.atMidLeft = require('./atMidLeft') -exports.atMid = require('./atMid') -exports.atMidRight = require('./atMidRight') exports.atNorm = require('./atNorm') -exports.atTopLeft = require('./atTopLeft') -exports.atTopMid = require('./atTopMid') -exports.atTopRight = require('./atTopRight') -exports.changeBasis = require('./changeBasis') exports.create = require('./create') From 2f3290a607ee967bb1c9353385ef2308bb308c1e Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 26 Oct 2022 18:38:18 +0300 Subject: [PATCH 21/41] impl rect2 .create .at .atNorm --- lib/index.js | 3 +-- lib/rect2/at.js | 6 ++++-- lib/rect2/atNorm.js | 10 ++++++---- lib/rect2/create.js | 16 ++++++---------- lib/rect2/index.js | 8 ++++++++ test/index.test.js | 1 + test/rect2/at.test.js | 37 +++++++++++++++++++++++++++++++++++++ test/rect2/atNorm.test.js | 37 +++++++++++++++++++++++++++++++++++++ test/rect2/create.test.js | 16 ++++++++++++++++ test/rect2/index.test.js | 12 ++++++++++++ 10 files changed, 128 insertions(+), 18 deletions(-) create mode 100644 test/rect2/at.test.js create mode 100644 test/rect2/atNorm.test.js create mode 100644 test/rect2/create.test.js create mode 100644 test/rect2/index.test.js diff --git a/lib/index.js b/lib/index.js index 9621ffe3..3e84eb8b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -44,8 +44,7 @@ exports.point3 = require('./point3') // TODO Ray. A line but into one direction only. // exports.ray3 = require('./ray3') -// TODO Rectangle on a two-dimensional plane. -// exports.rect2 = require('./rect2') +exports.rect2 = require('./rect2') // TODO rigid2 // TODO rigid3 diff --git a/lib/rect2/at.js b/lib/rect2/at.js index 77ed982e..7cf0f2d6 100644 --- a/lib/rect2/at.js +++ b/lib/rect2/at.js @@ -1,4 +1,6 @@ module.exports = (rect, rx, ry) => { + // affineplane.rect2.at(rect, rx, ry) + // // Take a point on the rect, represented on the rects outer basis. // // Parameters @@ -15,7 +17,7 @@ module.exports = (rect, rx, ry) => { // a point2 on the outer basis // return { - x: rect.a * rx - rect.b * ry + rect.x, - y: rect.b * rx + rect.a + ry + rect.y + x: rect.basis.a * rx - rect.basis.b * ry + rect.basis.x, + y: rect.basis.b * rx + rect.basis.a * ry + rect.basis.y } } diff --git a/lib/rect2/atNorm.js b/lib/rect2/atNorm.js index e2600cd7..e3155b25 100644 --- a/lib/rect2/atNorm.js +++ b/lib/rect2/atNorm.js @@ -1,18 +1,20 @@ const at = require('./at') module.exports = (rect, nw, nh) => { + // affineplane.rect2.atNorm(rect, nw, nh) + // // Take a point on the rect. // // Parameters // rect // a rectangle // nw - // normalized width 0..1 + // a number, a normalized coordinate along width 0..1 // nh - // normalized height 0..1 + // a number, a normalized coordinate along height 0..1 // // Return - // a point on the rect's outer basis + // a point on the rectangle's outer basis // - return at(rect, rect.w * nw, rect.h * nh) + return at(rect, rect.size.w * nw, rect.size.h * nh) } diff --git a/lib/rect2/create.js b/lib/rect2/create.js index 137749bf..6da939e4 100644 --- a/lib/rect2/create.js +++ b/lib/rect2/create.js @@ -1,13 +1,9 @@ -module.exports = (a, b, x, y, w, h) => { +module.exports = (basis, size) => { // Parameters: - // a - // b - // x - // y - // w - // width - // h - // height + // basis + // a plane2 + // size + // a size2 // - return { a, b, x, y, w, h } + return { basis, size } } diff --git a/lib/rect2/index.js b/lib/rect2/index.js index 20a6914e..3ace7203 100644 --- a/lib/rect2/index.js +++ b/lib/rect2/index.js @@ -1,3 +1,11 @@ +// affineplane.rect2 +// +// Rectangle on a two-dimensional plane. A rectangle is an object +// `{ basis: { a, b, x, y }, size: { w, h } }`, where the basis +// is a transformation from rectangle's inner coordinate system +// to the reference coordinate system. +// + exports.at = require('./at') exports.atNorm = require('./atNorm') exports.create = require('./create') diff --git a/test/index.test.js b/test/index.test.js index 2b7a29d7..53285adc 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -18,6 +18,7 @@ const units = { 'affineplane.plane3': require('./plane3/index.test'), 'affineplane.point2': require('./point2/index.test'), 'affineplane.point3': require('./point3/index.test'), + 'affineplane.rect2': require('./rect2/index.test'), 'affineplane.size2': require('./size2/index.test'), 'affineplane.vec2': require('./vec2/index.test'), 'affineplane.vec3': require('./vec3/index.test'), diff --git a/test/rect2/at.test.js b/test/rect2/at.test.js new file mode 100644 index 00000000..ce1e567f --- /dev/null +++ b/test/rect2/at.test.js @@ -0,0 +1,37 @@ +const affineplane = require('../../index') +const rect2 = affineplane.rect2 + +module.exports = (ts) => { + ts.test('case: basic at', (t) => { + + const r = { + basis: { a: 1, b: 0, x: 0, y: 0 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect2.at(r, 0, 0), + { x: 0, y: 0 }, + 'should be at top left corner' + ) + + const rr = { + basis: { a: 2, b: 0, x: 0, y: 0 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect2.at(rr, 0, 0), + { x: 0, y: 0 }, + 'scale keeps origin' + ) + + t.deepEqual( + rect2.at(rr, 1, 1), + { x: 2, y: 2 }, + 'should scale' + ) + + t.end() + }) +} diff --git a/test/rect2/atNorm.test.js b/test/rect2/atNorm.test.js new file mode 100644 index 00000000..021b3b16 --- /dev/null +++ b/test/rect2/atNorm.test.js @@ -0,0 +1,37 @@ +const affineplane = require('../../index') +const rect2 = affineplane.rect2 + +module.exports = (ts) => { + ts.test('case: basic atNorm', (t) => { + + const r = { + basis: { a: 1, b: 0, x: 0, y: 0 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect2.atNorm(r, 0, 0), + { x: 0, y: 0 }, + 'should be at top left corner' + ) + + const rr = { + basis: { a: 2, b: 0, x: 0, y: 0 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect2.atNorm(rr, 0, 0), + { x: 0, y: 0 }, + 'scale keeps origin' + ) + + t.deepEqual( + rect2.atNorm(rr, 1, 1), + { x: 20, y: 12 }, + 'should scale' + ) + + t.end() + }) +} diff --git a/test/rect2/create.test.js b/test/rect2/create.test.js new file mode 100644 index 00000000..5061348f --- /dev/null +++ b/test/rect2/create.test.js @@ -0,0 +1,16 @@ +const affineplane = require('../../index') +const rect2 = affineplane.rect2 + +module.exports = (ts) => { + ts.test('case: basic create', (t) => { + const basis = { a: 1, b: 0, x: 0, y: 0 } + const size = { w: 10, h: 6 } + t.deepEqual( + rect2.create(basis, size), + { basis, size }, + 'should have correct structure' + ) + + t.end() + }) +} diff --git a/test/rect2/index.test.js b/test/rect2/index.test.js new file mode 100644 index 00000000..b4990878 --- /dev/null +++ b/test/rect2/index.test.js @@ -0,0 +1,12 @@ +// A unit for each method. +const units = { + at: require('./at.test'), + atNorm: require('./atNorm.test'), + create: require('./create.test'), +} + +module.exports = (t) => { + Object.keys(units).forEach((unitName) => { + t.test('affineplane.rect2.' + unitName, units[unitName]) + }) +} From 8de48dc8c4988e84900de30e7441366b73c713c0 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 26 Oct 2022 18:38:51 +0300 Subject: [PATCH 22/41] impl rect2 .transitFrom .transitTo --- lib/rect2/index.js | 2 ++ lib/rect2/transitFrom.js | 21 ++++++++++++++++++++ lib/rect2/transitTo.js | 21 ++++++++++++++++++++ test/rect2/index.test.js | 2 ++ test/rect2/transitFrom.test.js | 36 ++++++++++++++++++++++++++++++++++ test/rect2/transitTo.test.js | 36 ++++++++++++++++++++++++++++++++++ 6 files changed, 118 insertions(+) create mode 100644 lib/rect2/transitFrom.js create mode 100644 lib/rect2/transitTo.js create mode 100644 test/rect2/transitFrom.test.js create mode 100644 test/rect2/transitTo.test.js diff --git a/lib/rect2/index.js b/lib/rect2/index.js index 3ace7203..655eb120 100644 --- a/lib/rect2/index.js +++ b/lib/rect2/index.js @@ -9,3 +9,5 @@ exports.at = require('./at') exports.atNorm = require('./atNorm') exports.create = require('./create') +exports.transitFrom = require('./transitFrom') +exports.transitTo = require('./transitTo') diff --git a/lib/rect2/transitFrom.js b/lib/rect2/transitFrom.js new file mode 100644 index 00000000..783044ee --- /dev/null +++ b/lib/rect2/transitFrom.js @@ -0,0 +1,21 @@ +const transitFrom = require('../plane2/transitFrom') + +module.exports = (rect, source) => { + // affineplane.rect2.transitFrom(rect, source) + // + // Convert a rectangle from source basis to the reference basis. + // + // Parameters + // rect + // a rect2, a rectangle on the source basis. + // source + // a plane2, the source basis represented on the reference basis. + // + // Return + // a rect2, represented on the reference basis. + // + return { + basis: transitFrom(rect.basis, source), + size: rect.size + } +} diff --git a/lib/rect2/transitTo.js b/lib/rect2/transitTo.js new file mode 100644 index 00000000..8172afdc --- /dev/null +++ b/lib/rect2/transitTo.js @@ -0,0 +1,21 @@ +const transitTo = require('../plane2/transitTo') + +module.exports = (rect, target) => { + // affineplane.rect2.transitTo(rect, target) + // + // Convert a rectangle from the reference basis to the target basis. + // + // Parameters + // rect + // a rectangle on the reference basis. + // target + // a plane2, the target basis represented on the reference basis. + // + // Return + // a rect2, represented on the target basis. + // + return { + basis: transitTo(rect.basis, target), + size: rect.size + } +} diff --git a/test/rect2/index.test.js b/test/rect2/index.test.js index b4990878..0179179e 100644 --- a/test/rect2/index.test.js +++ b/test/rect2/index.test.js @@ -3,6 +3,8 @@ const units = { at: require('./at.test'), atNorm: require('./atNorm.test'), create: require('./create.test'), + transitFrom: require('./transitFrom.test'), + transitTo: require('./transitTo.test') } module.exports = (t) => { diff --git a/test/rect2/transitFrom.test.js b/test/rect2/transitFrom.test.js new file mode 100644 index 00000000..ef998e55 --- /dev/null +++ b/test/rect2/transitFrom.test.js @@ -0,0 +1,36 @@ +const affineplane = require('../../index') +const rect2 = affineplane.rect2 + +module.exports = (ts) => { + ts.test('case: basic transitFrom', (t) => { + let rect, plane + + rect = { + basis: { a: 1, b: 0, x: 0, y: 0 }, + size: { w: 10, h: 6 } + } + plane = { a: 1, b: 0, x: 0, y: 0 } + + t.deepEqual( + rect2.transitFrom(rect, plane), + rect, + 'identity does not change rect' + ) + + rect = { + basis: { a: 2, b: 0, x: 2, y: 0 }, + size: { w: 10, h: 6 } + } + plane = { a: 2, b: 0, x: 0, y: 0 } + t.deepEqual( + rect2.transitFrom(rect, plane), + { + basis: { a: 4, b: 0, x: 4, y: 0 }, + size: { w: 10, h: 6 } + }, + 'should scale and translate' + ) + + t.end() + }) +} diff --git a/test/rect2/transitTo.test.js b/test/rect2/transitTo.test.js new file mode 100644 index 00000000..65f14b30 --- /dev/null +++ b/test/rect2/transitTo.test.js @@ -0,0 +1,36 @@ +const affineplane = require('../../index') +const rect2 = affineplane.rect2 + +module.exports = (ts) => { + ts.test('case: basic transitTo', (t) => { + let rect, plane + + rect = { + basis: { a: 1, b: 0, x: 0, y: 0 }, + size: { w: 10, h: 6 } + } + plane = { a: 1, b: 0, x: 0, y: 0 } + + t.deepEqual( + rect2.transitTo(rect, plane), + rect, + 'identity does not change rect' + ) + + rect = { + basis: { a: 2, b: 0, x: 2, y: 0 }, + size: { w: 10, h: 6 } + } + plane = { a: 2, b: 0, x: 0, y: 0 } + t.deepEqual( + rect2.transitTo(rect, plane), + { + basis: { a: 1, b: 0, x: 1, y: 0 }, + size: { w: 10, h: 6 } + }, + 'should scale and translate' + ) + + t.end() + }) +} From aaff7832bff42ce2ec9de318f22ba21f8adde1d9 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 26 Oct 2022 18:39:33 +0300 Subject: [PATCH 23/41] use pessimistic test reporter to inspect errors quickly --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3fa2117d..4d8b7704 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "lint": "standard index.js 'lib/**/*.js' 'test/**/*.js'", "lint:fix": "standard --fix index.js 'lib/**/*.js' 'test/**/*.js'", "test": "npm run lint && npm run test:unit", - "test:unit": "tape test/index.test.js | tap-arc", + "test:unit": "tape test/index.test.js | tap-arc --pessimistic", "test:watch": "nodemon -q --watch lib --watch test --exec 'npm --silent test'", "gv": "genversion lib/version.js", "build:docs": "node docs/generate.js", From 1cc24479828257d807439f91f3482d565a070bb3 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Wed, 26 Oct 2022 20:42:32 +0300 Subject: [PATCH 24/41] init rect2 fitScale fitSize --- lib/rect2/fitScale.js | 13 +++++++++++++ lib/rect2/fitSize.js | 6 ++++++ 2 files changed, 19 insertions(+) create mode 100644 lib/rect2/fitScale.js create mode 100644 lib/rect2/fitSize.js diff --git a/lib/rect2/fitScale.js b/lib/rect2/fitScale.js new file mode 100644 index 00000000..94bcc3e7 --- /dev/null +++ b/lib/rect2/fitScale.js @@ -0,0 +1,13 @@ +module.exports = (rect, target) => { + // TODO + // + // Parameters: + // rect + // a rect2 + // target + // a path2 or rect2 + // + // Return + // a rect2 + // +} diff --git a/lib/rect2/fitSize.js b/lib/rect2/fitSize.js new file mode 100644 index 00000000..9f893fed --- /dev/null +++ b/lib/rect2/fitSize.js @@ -0,0 +1,6 @@ +module.exports = (rect, target) => { + // TODO + // + // Change size properties to cover the target area. + // +} From 39fb6f83a99f4ad697ef5ecb22abe3d52acc8382 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 27 Oct 2022 14:09:30 +0300 Subject: [PATCH 25/41] impl rect2 getArea --- lib/rect2/getArea.js | 22 ++++++++++++++++++++++ lib/rect2/index.js | 1 + test/rect2/getArea.test.js | 31 +++++++++++++++++++++++++++++++ test/rect2/index.test.js | 1 + 4 files changed, 55 insertions(+) create mode 100644 lib/rect2/getArea.js create mode 100644 test/rect2/getArea.test.js diff --git a/lib/rect2/getArea.js b/lib/rect2/getArea.js new file mode 100644 index 00000000..b990a1b3 --- /dev/null +++ b/lib/rect2/getArea.js @@ -0,0 +1,22 @@ +const getScale = require('../plane2/getScale') + +module.exports = (rect) => { + // affineplane.rect2.getArea(rect) + // + // Compute rectangle area. This returns the area on the reference basis. + // + // Parameters + // rect + // a rect2, on the reference basis. + // + // Return + // a number, the area on the reference basis. + // + + // Area in inner basis + const la = rect.size.w * rect.size.h + // Dilation + const scale = getScale(rect.basis) + // Squared scale because area + return la * scale * scale +} diff --git a/lib/rect2/index.js b/lib/rect2/index.js index 655eb120..29348405 100644 --- a/lib/rect2/index.js +++ b/lib/rect2/index.js @@ -9,5 +9,6 @@ exports.at = require('./at') exports.atNorm = require('./atNorm') exports.create = require('./create') +exports.getArea = require('./getArea') exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') diff --git a/test/rect2/getArea.test.js b/test/rect2/getArea.test.js new file mode 100644 index 00000000..a5233669 --- /dev/null +++ b/test/rect2/getArea.test.js @@ -0,0 +1,31 @@ +const affineplane = require('../../index') +const rect2 = affineplane.rect2 + +module.exports = (ts) => { + ts.test('case: basic area', (t) => { + + const r = { + basis: { a: 1, b: 0, x: 0, y: 0 }, + size: { w: 10, h: 6 } + } + + t.equal( + rect2.getArea(r), + 60, + 'should have correct area' + ) + + const rr = { + basis: { a: 2, b: 0, x: 200, y: 200 }, + size: { w: 10, h: 6 } + } + + t.equal( + rect2.getArea(rr), + 240, + 'squared scale and translation should not affect' + ) + + t.end() + }) +} diff --git a/test/rect2/index.test.js b/test/rect2/index.test.js index 0179179e..a63ec2a9 100644 --- a/test/rect2/index.test.js +++ b/test/rect2/index.test.js @@ -3,6 +3,7 @@ const units = { at: require('./at.test'), atNorm: require('./atNorm.test'), create: require('./create.test'), + getArea: require('./getArea.test'), transitFrom: require('./transitFrom.test'), transitTo: require('./transitTo.test') } From c570bd7c2f2008c60837018e957bdbc31805b43c Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 27 Oct 2022 14:54:35 +0300 Subject: [PATCH 26/41] impl rect2 getBounds --- lib/rect2/getBounds.js | 67 ++++++++++++++++++++++++++++++++++++ lib/rect2/index.js | 1 + test/rect2/getBounds.test.js | 48 ++++++++++++++++++++++++++ test/rect2/index.test.js | 1 + 4 files changed, 117 insertions(+) create mode 100644 lib/rect2/getBounds.js create mode 100644 test/rect2/getBounds.test.js diff --git a/lib/rect2/getBounds.js b/lib/rect2/getBounds.js new file mode 100644 index 00000000..61450868 --- /dev/null +++ b/lib/rect2/getBounds.js @@ -0,0 +1,67 @@ +const atNorm = require('./atNorm') +const transformBy = require('../point2/transform') + +module.exports = (rect) => { + // affineplane.rect2.getBounds(rect) + // + // Get outer bounds of the given rect. In other words, + // return a rectangle with no rotation and no dilation with respect to + // the reference basis but has translation and size so that it covers + // the given rectangle. + // + // Parameters + // rect + // a rect2, on the reference basis. + // + // Return + // a rect2, on the reference basis. + // + + // Get corner points on the reference basis. + const corners = [ + atNorm(rect, 0, 0), + atNorm(rect, 1, 0), + atNorm(rect, 1, 1), + atNorm(rect, 0, 1) + ] + const n = 4 + + // Find min and max along x and y. + let minx = null + let maxx = null + let miny = null + let maxy = null + for (let i = 0; i < n; i += 1) { + const x = corners[i].x + const y = corners[i].y + if (minx === null) { + minx = x + } else { + minx = Math.min(x, minx) + } + if (maxx === null) { + maxx = x + } else { + maxx = Math.max(x, maxx) + } + if (miny === null) { + miny = y + } else { + miny = Math.min(y, miny) + } + if (maxy === null) { + maxy = y + } else { + maxy = Math.max(y, maxy) + } + } + + // Construct the bounding rect. + return { + basis: { a: 1, b: 0, x: minx, y: miny }, + size: { + w: maxx - minx, + h: maxy - miny + } + } +} diff --git a/lib/rect2/index.js b/lib/rect2/index.js index 29348405..38235ba2 100644 --- a/lib/rect2/index.js +++ b/lib/rect2/index.js @@ -10,5 +10,6 @@ exports.at = require('./at') exports.atNorm = require('./atNorm') exports.create = require('./create') exports.getArea = require('./getArea') +exports.getBounds = require('./getBounds') exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') diff --git a/test/rect2/getBounds.test.js b/test/rect2/getBounds.test.js new file mode 100644 index 00000000..0bece1c9 --- /dev/null +++ b/test/rect2/getBounds.test.js @@ -0,0 +1,48 @@ +const affineplane = require('../../index') +const rect2 = affineplane.rect2 + +module.exports = (ts) => { + ts.test('case: basic bounds', (t) => { + + const r = { + basis: { a: 1, b: 0, x: 0, y: 0 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect2.getBounds(r), + r, + 'should be itself' + ) + + const rr = { + basis: { a: 2, b: 0, x: 200, y: 200 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect2.getBounds(rr), + { + basis: { a: 1, b: 0, x: 200, y: 200 }, + size: { w: 20, h: 12 } + }, + 'should match translation' + ) + + const rrr = { + basis: { a: 0, b: 1, x: 200, y: 200 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect2.getBounds(rrr), + { + basis: { a: 1, b: 0, x: 194, y: 200 }, + size: { w: 6, h: 10 } + }, + 'should handle rotation' + ) + + t.end() + }) +} diff --git a/test/rect2/index.test.js b/test/rect2/index.test.js index a63ec2a9..fce4922d 100644 --- a/test/rect2/index.test.js +++ b/test/rect2/index.test.js @@ -4,6 +4,7 @@ const units = { atNorm: require('./atNorm.test'), create: require('./create.test'), getArea: require('./getArea.test'), + getBounds: require('./getBounds.test'), transitFrom: require('./transitFrom.test'), transitTo: require('./transitTo.test') } From d1f08c704708d5d108e73063fd20328be1ab064c Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 27 Oct 2022 14:54:55 +0300 Subject: [PATCH 27/41] plan api for fitScale --- lib/rect2/fitScale.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/rect2/fitScale.js b/lib/rect2/fitScale.js index 94bcc3e7..8dc8bd42 100644 --- a/lib/rect2/fitScale.js +++ b/lib/rect2/fitScale.js @@ -1,5 +1,8 @@ module.exports = (rect, target) => { // TODO + // alias scaleToFit, scaleToMatch, fitScaleInside + // scaleToFitInside, scaleToFitOutside, fitInsideByDilation + // fitOutsideByScale, scaleToMatch // // Parameters: // rect From 0f09426b350629e2a0167cb08af1f13afe29b407 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 27 Oct 2022 17:15:53 +0300 Subject: [PATCH 28/41] impl path2 .combine .transitFrom .transitTo --- lib/path2/combine.js | 16 ++++++++++++++++ lib/path2/index.js | 3 +++ lib/path2/transitFrom.js | 19 +++++++++++++++++++ lib/path2/transitTo.js | 24 ++++++++++++++++++++++++ test/index.test.js | 1 + test/path2/combine.test.js | 18 ++++++++++++++++++ test/path2/create.test.js | 15 +++++++++++++++ test/path2/index.test.js | 13 +++++++++++++ test/path2/transitFrom.test.js | 19 +++++++++++++++++++ test/path2/transitTo.test.js | 19 +++++++++++++++++++ 10 files changed, 147 insertions(+) create mode 100644 lib/path2/combine.js create mode 100644 lib/path2/transitFrom.js create mode 100644 lib/path2/transitTo.js create mode 100644 test/path2/combine.test.js create mode 100644 test/path2/create.test.js create mode 100644 test/path2/index.test.js create mode 100644 test/path2/transitFrom.test.js create mode 100644 test/path2/transitTo.test.js diff --git a/lib/path2/combine.js b/lib/path2/combine.js new file mode 100644 index 00000000..19ee19b7 --- /dev/null +++ b/lib/path2/combine.js @@ -0,0 +1,16 @@ +module.exports = (pa, pb) => { + // affineplane.path2.combine(pa, pb) + // + // Combine paths together. + // + // Parameters: + // pa + // a path2 + // pb + // a path2 + // + // Return + // a path2 + // + return pa.concat(pb) +} diff --git a/lib/path2/index.js b/lib/path2/index.js index 091f6984..db0c7625 100644 --- a/lib/path2/index.js +++ b/lib/path2/index.js @@ -5,4 +5,7 @@ // `[{ x, y }, { x, y }, ...]` // +exports.combine = require('./combine') exports.create = require('./create') +exports.transitFrom = require('./transitFrom') +exports.transitTo = require('./transitTo') diff --git a/lib/path2/transitFrom.js b/lib/path2/transitFrom.js new file mode 100644 index 00000000..48ec8824 --- /dev/null +++ b/lib/path2/transitFrom.js @@ -0,0 +1,19 @@ +const transitFrom = require('../point2/transitFrom') + +module.exports = (path, source) => { + // affineplane.path2.transitFrom(path, source) + // + // Represent a path on the reference basis. + // + // Parameters: + // path + // a path2, represented on the source basis. + // source + // a plane2, the source basis, represented + // .. on the reference basis. + // + // Return: + // a path2, represented on the reference basis. + // + return path.map(p => transitFrom(p, source)) +} diff --git a/lib/path2/transitTo.js b/lib/path2/transitTo.js new file mode 100644 index 00000000..a28741aa --- /dev/null +++ b/lib/path2/transitTo.js @@ -0,0 +1,24 @@ +const invert = require('../plane2/invert') +const transitFrom = require('./transitFrom') + +module.exports = (path, target) => { + // affineplane.path2.transitTo(path, target) + // + // Transit a path to the target basis. + // + // Parameters: + // path + // a path2, on the reference basis. + // target + // a plane2, the target basis, represented + // .. on the reference basis. + // + // Return: + // a path2, represented on the target basis. + // + + // The plane is a mapping from itself to a target basis. + // We need the projection from the target to the plane. + const itarget = invert(target) + return transitFrom(path, itarget) +} diff --git a/test/index.test.js b/test/index.test.js index 53285adc..27e35586 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -14,6 +14,7 @@ const units = { 'affineplane.line2': require('./line2/index.test'), 'affineplane.line3': require('./line3/index.test'), 'affineplane.quat4': require('./quat4/index.test'), + 'affineplane.path2': require('./path2/index.test'), 'affineplane.plane2': require('./plane2/index.test'), 'affineplane.plane3': require('./plane3/index.test'), 'affineplane.point2': require('./point2/index.test'), diff --git a/test/path2/combine.test.js b/test/path2/combine.test.js new file mode 100644 index 00000000..18de2ac8 --- /dev/null +++ b/test/path2/combine.test.js @@ -0,0 +1,18 @@ +const affineplane = require('../../index') +const path2 = affineplane.path2 + +module.exports = (ts) => { + ts.test('case: basic combine', (t) => { + + const p0 = [{ x: 0, y: 0 }, { x: 1, y: 1 }] + const p1 = [{ x: 2, y: 2 }] + + t.deepEqual( + path2.combine(p0, p1), + p0.concat(p1), + 'should copy arrays together' + ) + + t.end() + }) +} diff --git a/test/path2/create.test.js b/test/path2/create.test.js new file mode 100644 index 00000000..bbfb3564 --- /dev/null +++ b/test/path2/create.test.js @@ -0,0 +1,15 @@ +const affineplane = require('../../index') +const path2 = affineplane.path2 + +module.exports = (ts) => { + ts.test('case: basic create', (t) => { + + t.deepEqual( + path2.create([ { x: 0, y: 0, z: 0 }, { x: 1, y: 1 } ]), + [ { x: 0, y: 0 }, { x: 1, y: 1 } ], + 'should remove extra property' + ) + + t.end() + }) +} diff --git a/test/path2/index.test.js b/test/path2/index.test.js new file mode 100644 index 00000000..1e2d1bc6 --- /dev/null +++ b/test/path2/index.test.js @@ -0,0 +1,13 @@ +// A unit for each method. +const units = { + combine: require('./combine.test'), + create: require('./create.test'), + transitFrom: require('./transitFrom.test'), + transitTo: require('./transitTo.test') +} + +module.exports = (t) => { + Object.keys(units).forEach((unitName) => { + t.test('affineplane.path2.' + unitName, units[unitName]) + }) +} diff --git a/test/path2/transitFrom.test.js b/test/path2/transitFrom.test.js new file mode 100644 index 00000000..185151a5 --- /dev/null +++ b/test/path2/transitFrom.test.js @@ -0,0 +1,19 @@ +const affineplane = require('../../index') +const path2 = affineplane.path2 + +module.exports = (ts) => { + ts.test('case: basic transitFrom', (t) => { + let path, plane + + path = [{ x: 0, y: 0 }, { x: 1, y: 1 }] + plane = { a: 2, b: 0, x: 0, y: 2 } + + t.deepEqual( + path2.transitFrom(path, plane), + [{ x: 0, y: 2 }, { x: 2, y: 4 }], + 'scale and translation should affect' + ) + + t.end() + }) +} diff --git a/test/path2/transitTo.test.js b/test/path2/transitTo.test.js new file mode 100644 index 00000000..13b344a2 --- /dev/null +++ b/test/path2/transitTo.test.js @@ -0,0 +1,19 @@ +const affineplane = require('../../index') +const path2 = affineplane.path2 + +module.exports = (ts) => { + ts.test('case: basic transitTo', (t) => { + let path, plane + + path = [{ x: 0, y: 2 }, { x: 2, y: 4 }] + plane = { a: 2, b: 0, x: 0, y: 2 } + + t.deepEqual( + path2.transitTo(path, plane), + [{ x: 0, y: 0 }, { x: 1, y: 1 }], + 'scale and translation should affect' + ) + + t.end() + }) +} From d7526831024a2bc94cbf05d9e36d91770e229749 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 27 Oct 2022 17:16:08 +0300 Subject: [PATCH 29/41] correct doc point2.transitFrom --- lib/point2/transitFrom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/point2/transitFrom.js b/lib/point2/transitFrom.js index 0b9227c7..c1c14c04 100644 --- a/lib/point2/transitFrom.js +++ b/lib/point2/transitFrom.js @@ -1,6 +1,6 @@ module.exports = (point, source) => { - // affineplane.point2.transitFrom(point, plane) + // affineplane.point2.transitFrom(point, source) // // Transit a point2 from the source plane // to the reference plane. From ad3eb8c5e50521fa3a975018a02b7f54a6d40928 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 27 Oct 2022 17:21:34 +0300 Subject: [PATCH 30/41] alias helm3 helm2z --- lib/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index 3e84eb8b..96d9fd04 100644 --- a/lib/index.js +++ b/lib/index.js @@ -22,7 +22,8 @@ exports.epsilon = require('./epsilon') exports.helm2 = require('./helm2') exports.tran2 = exports.helm2 // TODO drop alias in v3 -exports.helm3 = require('./helm3') +exports.helm2z = require('./helm3') +exports.helm3 = exports.helm2z // TODO separate helm2z and helm3 in v3 exports.line2 = require('./line2') exports.line3 = require('./line3') From 3bba22a4c632d6049262312eae1401e522808247 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 27 Oct 2022 17:26:17 +0300 Subject: [PATCH 31/41] lint lib --- lib/quat4/estimator/estimate.test.js | 4 ++-- lib/rect2/getBounds.js | 1 - lib/vec3/rotateAroundAxis.js | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/quat4/estimator/estimate.test.js b/lib/quat4/estimator/estimate.test.js index 45b3b115..4c4760af 100644 --- a/lib/quat4/estimator/estimate.test.js +++ b/lib/quat4/estimator/estimate.test.js @@ -11,11 +11,11 @@ const domain = [ const range = [ { a: 0, b: -0.138, c: -0.161, d: 0.977 }, { a: 0, b: -0.977, c: -0.138, d: -0.161 }, - { a: 0, b: 0.161, c: -0.977, d: -0.138 }, + { a: 0, b: 0.161, c: -0.977, d: -0.138 } ] const est = estimate(domain, range) -const len = Math.sqrt(est.a**2 + est.b**2 + est.c**2 + est.d**2) +const len = Math.sqrt(est.a ** 2 + est.b ** 2 + est.c ** 2 + est.d ** 2) const angle = Math.acos(est.a / len) * 180 / Math.PI const mse = getmse(est, domain, range) diff --git a/lib/rect2/getBounds.js b/lib/rect2/getBounds.js index 61450868..78591dab 100644 --- a/lib/rect2/getBounds.js +++ b/lib/rect2/getBounds.js @@ -1,5 +1,4 @@ const atNorm = require('./atNorm') -const transformBy = require('../point2/transform') module.exports = (rect) => { // affineplane.rect2.getBounds(rect) diff --git a/lib/vec3/rotateAroundAxis.js b/lib/vec3/rotateAroundAxis.js index bd50d231..e242b206 100644 --- a/lib/vec3/rotateAroundAxis.js +++ b/lib/vec3/rotateAroundAxis.js @@ -1,4 +1,3 @@ -const ham = require('../quat4/hamilton') const norm = require('./magnitude') module.exports = function (v, axis, angle) { From 1bd2d57b3df860133325b38d9199fde1aed11df2 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 27 Oct 2022 17:26:23 +0300 Subject: [PATCH 32/41] lint test --- test/path2/combine.test.js | 1 - test/path2/create.test.js | 5 ++--- test/path2/transitFrom.test.js | 6 +++--- test/path2/transitTo.test.js | 6 +++--- test/rect2/at.test.js | 1 - test/rect2/atNorm.test.js | 1 - test/rect2/getArea.test.js | 1 - test/rect2/getBounds.test.js | 1 - 8 files changed, 8 insertions(+), 14 deletions(-) diff --git a/test/path2/combine.test.js b/test/path2/combine.test.js index 18de2ac8..20073dd6 100644 --- a/test/path2/combine.test.js +++ b/test/path2/combine.test.js @@ -3,7 +3,6 @@ const path2 = affineplane.path2 module.exports = (ts) => { ts.test('case: basic combine', (t) => { - const p0 = [{ x: 0, y: 0 }, { x: 1, y: 1 }] const p1 = [{ x: 2, y: 2 }] diff --git a/test/path2/create.test.js b/test/path2/create.test.js index bbfb3564..d87fd05c 100644 --- a/test/path2/create.test.js +++ b/test/path2/create.test.js @@ -3,10 +3,9 @@ const path2 = affineplane.path2 module.exports = (ts) => { ts.test('case: basic create', (t) => { - t.deepEqual( - path2.create([ { x: 0, y: 0, z: 0 }, { x: 1, y: 1 } ]), - [ { x: 0, y: 0 }, { x: 1, y: 1 } ], + path2.create([{ x: 0, y: 0, z: 0 }, { x: 1, y: 1 }]), + [{ x: 0, y: 0 }, { x: 1, y: 1 }], 'should remove extra property' ) diff --git a/test/path2/transitFrom.test.js b/test/path2/transitFrom.test.js index 185151a5..7b4f3f37 100644 --- a/test/path2/transitFrom.test.js +++ b/test/path2/transitFrom.test.js @@ -3,10 +3,10 @@ const path2 = affineplane.path2 module.exports = (ts) => { ts.test('case: basic transitFrom', (t) => { - let path, plane + // let path, plane - path = [{ x: 0, y: 0 }, { x: 1, y: 1 }] - plane = { a: 2, b: 0, x: 0, y: 2 } + const path = [{ x: 0, y: 0 }, { x: 1, y: 1 }] + const plane = { a: 2, b: 0, x: 0, y: 2 } t.deepEqual( path2.transitFrom(path, plane), diff --git a/test/path2/transitTo.test.js b/test/path2/transitTo.test.js index 13b344a2..8ba1cb3f 100644 --- a/test/path2/transitTo.test.js +++ b/test/path2/transitTo.test.js @@ -3,10 +3,10 @@ const path2 = affineplane.path2 module.exports = (ts) => { ts.test('case: basic transitTo', (t) => { - let path, plane + // let path, plane - path = [{ x: 0, y: 2 }, { x: 2, y: 4 }] - plane = { a: 2, b: 0, x: 0, y: 2 } + const path = [{ x: 0, y: 2 }, { x: 2, y: 4 }] + const plane = { a: 2, b: 0, x: 0, y: 2 } t.deepEqual( path2.transitTo(path, plane), diff --git a/test/rect2/at.test.js b/test/rect2/at.test.js index ce1e567f..fb8166e1 100644 --- a/test/rect2/at.test.js +++ b/test/rect2/at.test.js @@ -3,7 +3,6 @@ const rect2 = affineplane.rect2 module.exports = (ts) => { ts.test('case: basic at', (t) => { - const r = { basis: { a: 1, b: 0, x: 0, y: 0 }, size: { w: 10, h: 6 } diff --git a/test/rect2/atNorm.test.js b/test/rect2/atNorm.test.js index 021b3b16..d3c6a3d3 100644 --- a/test/rect2/atNorm.test.js +++ b/test/rect2/atNorm.test.js @@ -3,7 +3,6 @@ const rect2 = affineplane.rect2 module.exports = (ts) => { ts.test('case: basic atNorm', (t) => { - const r = { basis: { a: 1, b: 0, x: 0, y: 0 }, size: { w: 10, h: 6 } diff --git a/test/rect2/getArea.test.js b/test/rect2/getArea.test.js index a5233669..c9003ef8 100644 --- a/test/rect2/getArea.test.js +++ b/test/rect2/getArea.test.js @@ -3,7 +3,6 @@ const rect2 = affineplane.rect2 module.exports = (ts) => { ts.test('case: basic area', (t) => { - const r = { basis: { a: 1, b: 0, x: 0, y: 0 }, size: { w: 10, h: 6 } diff --git a/test/rect2/getBounds.test.js b/test/rect2/getBounds.test.js index 0bece1c9..7b31af41 100644 --- a/test/rect2/getBounds.test.js +++ b/test/rect2/getBounds.test.js @@ -3,7 +3,6 @@ const rect2 = affineplane.rect2 module.exports = (ts) => { ts.test('case: basic bounds', (t) => { - const r = { basis: { a: 1, b: 0, x: 0, y: 0 }, size: { w: 10, h: 6 } From e16ca4911539e6e0e3bd62ae1db62d479ef6a1d9 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 27 Oct 2022 17:51:13 +0300 Subject: [PATCH 33/41] init path3 --- lib/index.js | 2 ++ lib/path3/combine.js | 16 ++++++++++++++++ lib/path3/create.js | 14 ++++++++++++++ lib/path3/index.js | 9 +++++++++ test/index.test.js | 1 + test/path3/combine.test.js | 17 +++++++++++++++++ test/path3/create.test.js | 14 ++++++++++++++ test/path3/index.test.js | 11 +++++++++++ 8 files changed, 84 insertions(+) create mode 100644 lib/path3/combine.js create mode 100644 lib/path3/create.js create mode 100644 lib/path3/index.js create mode 100644 test/path3/combine.test.js create mode 100644 test/path3/create.test.js create mode 100644 test/path3/index.test.js diff --git a/lib/index.js b/lib/index.js index 96d9fd04..d0876f29 100644 --- a/lib/index.js +++ b/lib/index.js @@ -34,6 +34,8 @@ exports.line3 = require('./line3') exports.quat4 = require('./quat4') exports.path2 = require('./path2') +exports.path3 = require('./path3') + exports.poly2 = require('./poly2') exports.plane2 = require('./plane2') diff --git a/lib/path3/combine.js b/lib/path3/combine.js new file mode 100644 index 00000000..9a22604b --- /dev/null +++ b/lib/path3/combine.js @@ -0,0 +1,16 @@ +module.exports = (pa, pb) => { + // affineplane.path3.combine(pa, pb) + // + // Combine paths together. + // + // Parameters: + // pa + // a path3 + // pb + // a path3 + // + // Return + // a path3 + // + return pa.concat(pb) +} diff --git a/lib/path3/create.js b/lib/path3/create.js new file mode 100644 index 00000000..d75c65b3 --- /dev/null +++ b/lib/path3/create.js @@ -0,0 +1,14 @@ +module.exports = (points) => { + // affineplane.path3.create(points) + // + // Create a path on plane. Deep-clones the points array. + // + // Parameters: + // points + // array of point3 + // + // Return: + // a path3, array of points + // + return points.map(p => { return { x: p.x, y: p.y, z: p.z } }) +} diff --git a/lib/path3/index.js b/lib/path3/index.js new file mode 100644 index 00000000..e6df83a5 --- /dev/null +++ b/lib/path3/index.js @@ -0,0 +1,9 @@ +// affineplane.path3 +// +// Three-dimensional path; Array of point3; Open sequence of points; +// Does not form a polygon but a sequence of line segments. +// `[{ x, y, z }, { x, y, z }, ...]` +// + +exports.combine = require('./combine') +exports.create = require('./create') diff --git a/test/index.test.js b/test/index.test.js index 27e35586..47efea1d 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -15,6 +15,7 @@ const units = { 'affineplane.line3': require('./line3/index.test'), 'affineplane.quat4': require('./quat4/index.test'), 'affineplane.path2': require('./path2/index.test'), + 'affineplane.path3': require('./path3/index.test'), 'affineplane.plane2': require('./plane2/index.test'), 'affineplane.plane3': require('./plane3/index.test'), 'affineplane.point2': require('./point2/index.test'), diff --git a/test/path3/combine.test.js b/test/path3/combine.test.js new file mode 100644 index 00000000..ae729655 --- /dev/null +++ b/test/path3/combine.test.js @@ -0,0 +1,17 @@ +const affineplane = require('../../index') +const path3 = affineplane.path3 + +module.exports = (ts) => { + ts.test('case: basic combine', (t) => { + const p0 = [{ x: 0, y: 0, z: 0 }, { x: 1, y: 1, z: 1 }] + const p1 = [{ x: 2, y: 2, z: 2 }] + + t.deepEqual( + path3.combine(p0, p1), + p0.concat(p1), + 'should copy arrays together' + ) + + t.end() + }) +} diff --git a/test/path3/create.test.js b/test/path3/create.test.js new file mode 100644 index 00000000..3b615a39 --- /dev/null +++ b/test/path3/create.test.js @@ -0,0 +1,14 @@ +const affineplane = require('../../index') +const path3 = affineplane.path3 + +module.exports = (ts) => { + ts.test('case: basic create', (t) => { + t.deepEqual( + path3.create([{ x: 0, y: 0, z: 0 }, { x: 1, y: 1, z: 1, w: 2 }]), + [{ x: 0, y: 0, z: 0 }, { x: 1, y: 1, z: 1 }], + 'should remove extra property' + ) + + t.end() + }) +} diff --git a/test/path3/index.test.js b/test/path3/index.test.js new file mode 100644 index 00000000..e5959dcf --- /dev/null +++ b/test/path3/index.test.js @@ -0,0 +1,11 @@ +// A unit for each method. +const units = { + combine: require('./combine.test'), + create: require('./create.test') +} + +module.exports = (t) => { + Object.keys(units).forEach((unitName) => { + t.test('affineplane.path3.' + unitName, units[unitName]) + }) +} From 7c742f8fbc922af0bd05a947bc8fdef4066e9e87 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 27 Oct 2022 17:56:18 +0300 Subject: [PATCH 34/41] impl path3 transitFrom transitTo --- lib/path3/index.js | 2 ++ lib/path3/transitFrom.js | 19 +++++++++++++++++++ lib/path3/transitTo.js | 24 ++++++++++++++++++++++++ test/path3/index.test.js | 4 +++- test/path3/transitFrom.test.js | 19 +++++++++++++++++++ test/path3/transitTo.test.js | 19 +++++++++++++++++++ 6 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 lib/path3/transitFrom.js create mode 100644 lib/path3/transitTo.js create mode 100644 test/path3/transitFrom.test.js create mode 100644 test/path3/transitTo.test.js diff --git a/lib/path3/index.js b/lib/path3/index.js index e6df83a5..5d7b02cf 100644 --- a/lib/path3/index.js +++ b/lib/path3/index.js @@ -7,3 +7,5 @@ exports.combine = require('./combine') exports.create = require('./create') +exports.transitFrom = require('./transitFrom') +exports.transitTo = require('./transitTo') diff --git a/lib/path3/transitFrom.js b/lib/path3/transitFrom.js new file mode 100644 index 00000000..35271994 --- /dev/null +++ b/lib/path3/transitFrom.js @@ -0,0 +1,19 @@ +const transitFrom = require('../point3/transitFrom') + +module.exports = (path, source) => { + // affineplane.path3.transitFrom(path, source) + // + // Represent a path on the reference basis. + // + // Parameters: + // path + // a path3, represented on the source basis. + // source + // a plane3, the source basis, represented + // .. on the reference basis. + // + // Return: + // a path3, represented on the reference basis. + // + return path.map(p => transitFrom(p, source)) +} diff --git a/lib/path3/transitTo.js b/lib/path3/transitTo.js new file mode 100644 index 00000000..a3677385 --- /dev/null +++ b/lib/path3/transitTo.js @@ -0,0 +1,24 @@ +const invert = require('../plane3/invert') +const transitFrom = require('./transitFrom') + +module.exports = (path, target) => { + // affineplane.path3.transitTo(path, target) + // + // Transit a path to the target basis. + // + // Parameters: + // path + // a path3, on the reference basis. + // target + // a plane3, the target basis, represented + // .. on the reference basis. + // + // Return: + // a path3, represented on the target basis. + // + + // The plane is a mapping from itself to a target basis. + // We need the projection from the target to the plane. + const itarget = invert(target) + return transitFrom(path, itarget) +} diff --git a/test/path3/index.test.js b/test/path3/index.test.js index e5959dcf..721bdcc8 100644 --- a/test/path3/index.test.js +++ b/test/path3/index.test.js @@ -1,7 +1,9 @@ // A unit for each method. const units = { combine: require('./combine.test'), - create: require('./create.test') + create: require('./create.test'), + transitFrom: require('./transitFrom.test'), + transitTo: require('./transitTo.test') } module.exports = (t) => { diff --git a/test/path3/transitFrom.test.js b/test/path3/transitFrom.test.js new file mode 100644 index 00000000..d880aeae --- /dev/null +++ b/test/path3/transitFrom.test.js @@ -0,0 +1,19 @@ +const affineplane = require('../../index') +const path3 = affineplane.path3 + +module.exports = (ts) => { + ts.test('case: basic transitFrom', (t) => { + // let path, plane + + const path = [{ x: 0, y: 0, z: 0 }, { x: 1, y: 1, z: 1 }] + const plane = { a: 2, b: 0, x: 0, y: 2, z: 0 } + + t.deepEqual( + path3.transitFrom(path, plane), + [{ x: 0, y: 2, z: 0 }, { x: 2, y: 4, z: 2 }], + 'scale and translation should affect' + ) + + t.end() + }) +} diff --git a/test/path3/transitTo.test.js b/test/path3/transitTo.test.js new file mode 100644 index 00000000..82f1da7c --- /dev/null +++ b/test/path3/transitTo.test.js @@ -0,0 +1,19 @@ +const affineplane = require('../../index') +const path3 = affineplane.path3 + +module.exports = (ts) => { + ts.test('case: basic transitTo', (t) => { + // let path, plane + + const path = [{ x: 0, y: 2, z: 0 }, { x: 2, y: 4, z: 2 }] + const plane = { a: 2, b: 0, x: 0, y: 2, z: 0 } + + t.deepEqual( + path3.transitTo(path, plane), + [{ x: 0, y: 0, z: 0 }, { x: 1, y: 1, z: 1 }], + 'scale and translation should affect' + ) + + t.end() + }) +} From a9e2565ff0bbc8bf26bc5ea77813682c5b75b212 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 27 Oct 2022 22:25:23 +0300 Subject: [PATCH 35/41] alias basis2 plane2 and basis3 plane3 --- lib/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/index.js b/lib/index.js index d0876f29..affa15a3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -9,6 +9,9 @@ exports.angle = require('./angle') +exports.basis2 = require('./plane2') +exports.basis3 = require('./plane3') + exports.dir2 = require('./dir2') exports.dir3 = require('./dir3') @@ -38,8 +41,8 @@ exports.path3 = require('./path3') exports.poly2 = require('./poly2') -exports.plane2 = require('./plane2') -exports.plane3 = require('./plane3') +exports.plane2 = exports.basis2 +exports.plane3 = exports.basis3 exports.point2 = require('./point2') exports.point3 = require('./point3') From ddfa97b53118099fd176d88a28de627cc01dc10b Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Fri, 28 Oct 2022 00:43:22 +0300 Subject: [PATCH 36/41] impl rect3 --- lib/index.js | 1 + lib/rect3/at.js | 24 ++++++++++++ lib/rect3/atNorm.js | 20 ++++++++++ lib/rect3/create.js | 16 ++++++++ lib/rect3/getArea.js | 22 +++++++++++ lib/rect3/getBounds.js | 69 ++++++++++++++++++++++++++++++++++ lib/rect3/index.js | 15 ++++++++ lib/rect3/transitFrom.js | 21 +++++++++++ lib/rect3/transitTo.js | 21 +++++++++++ test/index.test.js | 1 + test/rect3/at.test.js | 36 ++++++++++++++++++ test/rect3/atNorm.test.js | 36 ++++++++++++++++++ test/rect3/create.test.js | 16 ++++++++ test/rect3/getArea.test.js | 30 +++++++++++++++ test/rect3/getBounds.test.js | 47 +++++++++++++++++++++++ test/rect3/index.test.js | 16 ++++++++ test/rect3/transitFrom.test.js | 36 ++++++++++++++++++ test/rect3/transitTo.test.js | 36 ++++++++++++++++++ 18 files changed, 463 insertions(+) create mode 100644 lib/rect3/at.js create mode 100644 lib/rect3/atNorm.js create mode 100644 lib/rect3/create.js create mode 100644 lib/rect3/getArea.js create mode 100644 lib/rect3/getBounds.js create mode 100644 lib/rect3/index.js create mode 100644 lib/rect3/transitFrom.js create mode 100644 lib/rect3/transitTo.js create mode 100644 test/rect3/at.test.js create mode 100644 test/rect3/atNorm.test.js create mode 100644 test/rect3/create.test.js create mode 100644 test/rect3/getArea.test.js create mode 100644 test/rect3/getBounds.test.js create mode 100644 test/rect3/index.test.js create mode 100644 test/rect3/transitFrom.test.js create mode 100644 test/rect3/transitTo.test.js diff --git a/lib/index.js b/lib/index.js index affa15a3..df311e26 100644 --- a/lib/index.js +++ b/lib/index.js @@ -51,6 +51,7 @@ exports.point3 = require('./point3') // exports.ray3 = require('./ray3') exports.rect2 = require('./rect2') +exports.rect3 = require('./rect3') // TODO rigid2 // TODO rigid3 diff --git a/lib/rect3/at.js b/lib/rect3/at.js new file mode 100644 index 00000000..27e35191 --- /dev/null +++ b/lib/rect3/at.js @@ -0,0 +1,24 @@ +module.exports = (rect, rx, ry) => { + // affineplane.rect3.at(rect, rx, ry) + // + // Take a point on the rect, represented on the rects outer basis. + // + // Parameters + // rect + // a rect2 + // rx + // horizontal distance from the top-left corner of the rectangle + // .. represented on the rects inner basis + // ry + // vertical distance from the top-left corner of the rectangle + // .. represented on the rects inner basis + // + // Return + // a point3, represented on the outer basis. + // + return { + x: rect.basis.a * rx - rect.basis.b * ry + rect.basis.x, + y: rect.basis.b * rx + rect.basis.a * ry + rect.basis.y, + z: rect.basis.z + } +} diff --git a/lib/rect3/atNorm.js b/lib/rect3/atNorm.js new file mode 100644 index 00000000..27733fbc --- /dev/null +++ b/lib/rect3/atNorm.js @@ -0,0 +1,20 @@ +const at = require('./at') + +module.exports = (rect, nw, nh) => { + // affineplane.rect3.atNorm(rect, nw, nh) + // + // Take a point on the rect. + // + // Parameters + // rect + // a rectangle + // nw + // a number, a normalized coordinate along width 0..1 + // nh + // a number, a normalized coordinate along height 0..1 + // + // Return + // a point3, in the rectangle's outer basis + // + return at(rect, rect.size.w * nw, rect.size.h * nh) +} diff --git a/lib/rect3/create.js b/lib/rect3/create.js new file mode 100644 index 00000000..43c56d3d --- /dev/null +++ b/lib/rect3/create.js @@ -0,0 +1,16 @@ +module.exports = (basis, size) => { + // affineplane.rect3.create(basis, size) + // + // Construct a rect3 object from basis and size. + // + // Parameters: + // basis + // a basis3 + // size + // a size2 + // + // Return: + // a rect3 + // + return { basis, size } +} diff --git a/lib/rect3/getArea.js b/lib/rect3/getArea.js new file mode 100644 index 00000000..6085cffa --- /dev/null +++ b/lib/rect3/getArea.js @@ -0,0 +1,22 @@ +const getScale = require('../plane3/getScale') + +module.exports = (rect) => { + // affineplane.rect2.getArea(rect) + // + // Compute rectangle area. This returns the area on the reference basis. + // + // Parameters + // rect + // a rect2, on the reference basis. + // + // Return + // a number, the area on the reference basis. + // + + // Area in inner basis + const la = rect.size.w * rect.size.h + // Dilation + const scale = getScale(rect.basis) + // Squared scale because area + return la * scale * scale +} diff --git a/lib/rect3/getBounds.js b/lib/rect3/getBounds.js new file mode 100644 index 00000000..17b977a2 --- /dev/null +++ b/lib/rect3/getBounds.js @@ -0,0 +1,69 @@ +const atNorm = require('./atNorm') + +module.exports = (rect) => { + // affineplane.rect2.getBounds(rect) + // + // Get outer bounds of the given rect. In other words, + // return a rectangle with no rotation and no dilation with respect to + // the reference basis but has translation and size so that it covers + // the given rectangle. + // + // Parameters + // rect + // a rect2, on the reference basis. + // + // Return + // a rect2, on the reference basis. + // + + // Get corner points on the reference basis. + const corners = [ + atNorm(rect, 0, 0), + atNorm(rect, 1, 0), + atNorm(rect, 1, 1), + atNorm(rect, 0, 1) + ] + const n = 4 + + // Find min and max along x and y. + let minx = null + let maxx = null + let miny = null + let maxy = null + for (let i = 0; i < n; i += 1) { + const x = corners[i].x + const y = corners[i].y + if (minx === null) { + minx = x + } else { + minx = Math.min(x, minx) + } + if (maxx === null) { + maxx = x + } else { + maxx = Math.max(x, maxx) + } + if (miny === null) { + miny = y + } else { + miny = Math.min(y, miny) + } + if (maxy === null) { + maxy = y + } else { + maxy = Math.max(y, maxy) + } + } + + // Preserve z coordinate. + const prez = rect.basis.z + + // Construct the bounding rect. + return { + basis: { a: 1, b: 0, x: minx, y: miny, z: prez }, + size: { + w: maxx - minx, + h: maxy - miny + } + } +} diff --git a/lib/rect3/index.js b/lib/rect3/index.js new file mode 100644 index 00000000..0a0f2cc4 --- /dev/null +++ b/lib/rect3/index.js @@ -0,0 +1,15 @@ +// affineplane.rect3 +// +// A rectangle, floating in three dimensions. A rect3 is an object +// `{ basis: { a, b, x, y, z }, size: { w, h } }`, where the basis +// is a helm3 - a Helmert transformation from rectangle's inner coordinate +// system to the reference coordinate system. +// + +exports.at = require('./at') +exports.atNorm = require('./atNorm') +exports.create = require('./create') +exports.getArea = require('./getArea') +exports.getBounds = require('./getBounds') +exports.transitFrom = require('./transitFrom') +exports.transitTo = require('./transitTo') diff --git a/lib/rect3/transitFrom.js b/lib/rect3/transitFrom.js new file mode 100644 index 00000000..83784d37 --- /dev/null +++ b/lib/rect3/transitFrom.js @@ -0,0 +1,21 @@ +const transitFrom = require('../plane3/transitFrom') + +module.exports = (rect, source) => { + // affineplane.rect3.transitFrom(rect, source) + // + // Convert a rectangle from source basis to the reference basis. + // + // Parameters + // rect + // a rect3, a rectangle on the source basis. + // source + // a basis3, the source basis represented on the reference basis. + // + // Return + // a rect3, represented on the reference basis. + // + return { + basis: transitFrom(rect.basis, source), + size: rect.size + } +} diff --git a/lib/rect3/transitTo.js b/lib/rect3/transitTo.js new file mode 100644 index 00000000..f7059eb1 --- /dev/null +++ b/lib/rect3/transitTo.js @@ -0,0 +1,21 @@ +const transitTo = require('../plane3/transitTo') + +module.exports = (rect, target) => { + // affineplane.rect3.transitTo(rect, target) + // + // Convert a rectangle from the reference basis to the target basis. + // + // Parameters + // rect + // a rectangle on the reference basis. + // target + // a basis3, the target basis represented on the reference basis. + // + // Return + // a rect3, represented on the target basis. + // + return { + basis: transitTo(rect.basis, target), + size: rect.size + } +} diff --git a/test/index.test.js b/test/index.test.js index 47efea1d..06a156f3 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -21,6 +21,7 @@ const units = { 'affineplane.point2': require('./point2/index.test'), 'affineplane.point3': require('./point3/index.test'), 'affineplane.rect2': require('./rect2/index.test'), + 'affineplane.rect3': require('./rect3/index.test'), 'affineplane.size2': require('./size2/index.test'), 'affineplane.vec2': require('./vec2/index.test'), 'affineplane.vec3': require('./vec3/index.test'), diff --git a/test/rect3/at.test.js b/test/rect3/at.test.js new file mode 100644 index 00000000..6dab0862 --- /dev/null +++ b/test/rect3/at.test.js @@ -0,0 +1,36 @@ +const affineplane = require('../../index') +const rect3 = affineplane.rect3 + +module.exports = (ts) => { + ts.test('case: basic at', (t) => { + const r = { + basis: { a: 1, b: 0, x: 0, y: 0, z: 2 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect3.at(r, 0, 0), + { x: 0, y: 0, z: 2 }, + 'should be at top left corner, preserve z' + ) + + const rr = { + basis: { a: 2, b: 0, x: 0, y: 0, z: 3 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect3.at(rr, 0, 0), + { x: 0, y: 0, z: 3 }, + 'scale keeps origin' + ) + + t.deepEqual( + rect3.at(rr, 1, 1), + { x: 2, y: 2, z: 3 }, + 'should scale' + ) + + t.end() + }) +} diff --git a/test/rect3/atNorm.test.js b/test/rect3/atNorm.test.js new file mode 100644 index 00000000..526714eb --- /dev/null +++ b/test/rect3/atNorm.test.js @@ -0,0 +1,36 @@ +const affineplane = require('../../index') +const rect3 = affineplane.rect3 + +module.exports = (ts) => { + ts.test('case: basic atNorm', (t) => { + const r = { + basis: { a: 1, b: 0, x: 0, y: 0, z: 2 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect3.atNorm(r, 0, 0), + { x: 0, y: 0, z: 2 }, + 'should be at top left corner' + ) + + const rr = { + basis: { a: 2, b: 0, x: 0, y: 0, z: 3 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect3.atNorm(rr, 0, 0), + { x: 0, y: 0, z: 3 }, + 'scale keeps origin' + ) + + t.deepEqual( + rect3.atNorm(rr, 1, 1), + { x: 20, y: 12, z: 3 }, + 'should scale' + ) + + t.end() + }) +} diff --git a/test/rect3/create.test.js b/test/rect3/create.test.js new file mode 100644 index 00000000..89323507 --- /dev/null +++ b/test/rect3/create.test.js @@ -0,0 +1,16 @@ +const affineplane = require('../../index') +const rect3 = affineplane.rect3 + +module.exports = (ts) => { + ts.test('case: basic create', (t) => { + const basis = { a: 1, b: 0, x: 0, y: 0, z: 0 } + const size = { w: 10, h: 6 } + t.deepEqual( + rect3.create(basis, size), + { basis, size }, + 'should have correct structure' + ) + + t.end() + }) +} diff --git a/test/rect3/getArea.test.js b/test/rect3/getArea.test.js new file mode 100644 index 00000000..6d66c763 --- /dev/null +++ b/test/rect3/getArea.test.js @@ -0,0 +1,30 @@ +const affineplane = require('../../index') +const rect3 = affineplane.rect3 + +module.exports = (ts) => { + ts.test('case: basic area', (t) => { + const r = { + basis: { a: 1, b: 0, x: 0, y: 0, z: 2 }, + size: { w: 10, h: 6 } + } + + t.equal( + rect3.getArea(r), + 60, + 'should have correct area' + ) + + const rr = { + basis: { a: 2, b: 0, x: 200, y: 200, z: 2 }, + size: { w: 10, h: 6 } + } + + t.equal( + rect3.getArea(rr), + 240, + 'squared scale and translation should not affect' + ) + + t.end() + }) +} diff --git a/test/rect3/getBounds.test.js b/test/rect3/getBounds.test.js new file mode 100644 index 00000000..aa6c29b4 --- /dev/null +++ b/test/rect3/getBounds.test.js @@ -0,0 +1,47 @@ +const affineplane = require('../../index') +const rect3 = affineplane.rect3 + +module.exports = (ts) => { + ts.test('case: basic bounds', (t) => { + const r = { + basis: { a: 1, b: 0, x: 0, y: 0, z: 2 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect3.getBounds(r), + r, + 'should be itself' + ) + + const rr = { + basis: { a: 2, b: 0, x: 200, y: 200, z: 20 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect3.getBounds(rr), + { + basis: { a: 1, b: 0, x: 200, y: 200, z: 20 }, + size: { w: 20, h: 12 } + }, + 'should match translation' + ) + + const rrr = { + basis: { a: 0, b: 1, x: 200, y: 200, z: 20 }, + size: { w: 10, h: 6 } + } + + t.deepEqual( + rect3.getBounds(rrr), + { + basis: { a: 1, b: 0, x: 194, y: 200, z: 20 }, + size: { w: 6, h: 10 } + }, + 'should handle rotation' + ) + + t.end() + }) +} diff --git a/test/rect3/index.test.js b/test/rect3/index.test.js new file mode 100644 index 00000000..85aa7ec8 --- /dev/null +++ b/test/rect3/index.test.js @@ -0,0 +1,16 @@ +// A unit for each method. +const units = { + at: require('./at.test'), + atNorm: require('./atNorm.test'), + create: require('./create.test'), + getArea: require('./getArea.test'), + getBounds: require('./getBounds.test'), + transitFrom: require('./transitFrom.test'), + transitTo: require('./transitTo.test') +} + +module.exports = (t) => { + Object.keys(units).forEach((unitName) => { + t.test('affineplane.rect3.' + unitName, units[unitName]) + }) +} diff --git a/test/rect3/transitFrom.test.js b/test/rect3/transitFrom.test.js new file mode 100644 index 00000000..58a6bac7 --- /dev/null +++ b/test/rect3/transitFrom.test.js @@ -0,0 +1,36 @@ +const affineplane = require('../../index') +const rect3 = affineplane.rect3 + +module.exports = (ts) => { + ts.test('case: basic transitFrom', (t) => { + let rect, plane + + rect = { + basis: { a: 1, b: 0, x: 0, y: 0, z: 0 }, + size: { w: 10, h: 6 } + } + plane = { a: 1, b: 0, x: 0, y: 0, z: 0 } + + t.deepEqual( + rect3.transitFrom(rect, plane), + rect, + 'identity does not change rect' + ) + + rect = { + basis: { a: 2, b: 0, x: 2, y: 0, z: 2 }, + size: { w: 10, h: 6 } + } + plane = { a: 2, b: 0, x: 0, y: 0, z: 3 } + t.deepEqual( + rect3.transitFrom(rect, plane), + { + basis: { a: 4, b: 0, x: 4, y: 0, z: 7 }, // 7: dz=2 at m=2 + dz=3 + size: { w: 10, h: 6 } + }, + 'should scale and translate' + ) + + t.end() + }) +} diff --git a/test/rect3/transitTo.test.js b/test/rect3/transitTo.test.js new file mode 100644 index 00000000..398930a0 --- /dev/null +++ b/test/rect3/transitTo.test.js @@ -0,0 +1,36 @@ +const affineplane = require('../../index') +const rect3 = affineplane.rect3 + +module.exports = (ts) => { + ts.test('case: basic transitTo', (t) => { + let rect, plane + + rect = { + basis: { a: 1, b: 0, x: 0, y: 0, z: 2 }, + size: { w: 10, h: 6 } + } + plane = { a: 1, b: 0, x: 0, y: 0, z: 0 } + + t.deepEqual( + rect3.transitTo(rect, plane), + rect, + 'identity does not change rect' + ) + + rect = { + basis: { a: 2, b: 0, x: 2, y: 0, z: 2 }, + size: { w: 10, h: 6 } + } + plane = { a: 2, b: 0, x: 0, y: 0, z: 2 } + t.deepEqual( + rect3.transitTo(rect, plane), + { + basis: { a: 1, b: 0, x: 1, y: 0, z: 0 }, + size: { w: 10, h: 6 } + }, + 'should scale and translate' + ) + + t.end() + }) +} From baab81b10a92a7568810ce764fe77f27d79a9e14 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Fri, 28 Oct 2022 14:34:13 +0300 Subject: [PATCH 37/41] impl path3.projectTo --- lib/path3/index.js | 1 + lib/path3/projectTo.js | 33 +++++++++++++++++++++++++++++++++ lib/vec3/projectTo.js | 8 +++++--- test/path3/index.test.js | 1 + test/path3/projectTo.test.js | 26 ++++++++++++++++++++++++++ 5 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 lib/path3/projectTo.js create mode 100644 test/path3/projectTo.test.js diff --git a/lib/path3/index.js b/lib/path3/index.js index 5d7b02cf..3277378c 100644 --- a/lib/path3/index.js +++ b/lib/path3/index.js @@ -7,5 +7,6 @@ exports.combine = require('./combine') exports.create = require('./create') +exports.projectTo = require('./projectTo') exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') diff --git a/lib/path3/projectTo.js b/lib/path3/projectTo.js new file mode 100644 index 00000000..e52b2777 --- /dev/null +++ b/lib/path3/projectTo.js @@ -0,0 +1,33 @@ +const transitTo = require('../path2/transitTo') +const projectTo = require('../point3/projectTo') + +module.exports = (path, plane, camera) => { + // affineplane.path3.projectTo(path, plane[, camera]) + // + // Project a 3D path onto a 2D plane orthogonally. + // Project perspectively if a camera position is given. + // + // Parameters: + // path + // a path3, in the reference basis. + // plane + // a plane3, in the reference basis. + // .. The image plane to which to project. + // camera + // a point3, in the reference basis. The location of the camera. + // + // Return: + // a path2 on the image plane. + // + + // If camera present, project perspectively. + if (camera) { + // Implement via point projections. + return path.map(p => projectTo(p, plane, camera)) + } + + // Orthogonal projection to the target plane. + // In this case it is equivalent to 2d transitTo. + // The z dimension is lost. + return transitTo(path, plane) +} diff --git a/lib/vec3/projectTo.js b/lib/vec3/projectTo.js index 46da22b7..c208978b 100644 --- a/lib/vec3/projectTo.js +++ b/lib/vec3/projectTo.js @@ -8,13 +8,15 @@ module.exports = (vec, plane) => { // they have no fixed position and the perspective position // depends on the position. See affineplane.line3.projectTo // for perspective projection of a vector with fixed position. + // See affineplane.path3.projectTo for perspective projection of + // a sequence of points. // // Parameters: // vec - // a vec3 in the reference space. + // a vec3, in the reference basis. // plane - // a plane3 in the reference space. - // .. The image plane. + // a plane3, in the reference basis. + // .. The image plane to which to project. // // Return: // a vec2 on the image plane. diff --git a/test/path3/index.test.js b/test/path3/index.test.js index 721bdcc8..833f5bf1 100644 --- a/test/path3/index.test.js +++ b/test/path3/index.test.js @@ -2,6 +2,7 @@ const units = { combine: require('./combine.test'), create: require('./create.test'), + projectTo: require('./projectTo.test'), transitFrom: require('./transitFrom.test'), transitTo: require('./transitTo.test') } diff --git a/test/path3/projectTo.test.js b/test/path3/projectTo.test.js new file mode 100644 index 00000000..5eda1692 --- /dev/null +++ b/test/path3/projectTo.test.js @@ -0,0 +1,26 @@ +const affineplane = require('../../index') +const path3 = affineplane.path3 + +module.exports = (ts) => { + ts.test('case: basic projectTo', (t) => { + // let path, plane + + const path = [{ x: 0, y: 0, z: 0 }, { x: 1, y: 1, z: 1 }] + const plane = { a: 2, b: 0, x: 0, y: 2, z: -2 } + + t.deepEqual( + path3.projectTo(path, plane), + [{ x: 0, y: -1 }, { x: 0.5, y: -0.5 }], + 'orthogonal projection should be equivalent to 2D transitTo' + ) + + const camera = { x: 0, y: 0, z: -5 } + t.deepEqual( + path3.projectTo(path, plane, camera), + [{ x: 0, y: -1 }, { x: 0.25, y: -0.75 }], + 'orthogonal projection should be equivalent to transitTo' + ) + + t.end() + }) +} From 70cf6e0540e27eb844f9fb24ffb21451e68a9def Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Fri, 28 Oct 2022 14:39:03 +0300 Subject: [PATCH 38/41] add 3d package tag --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 4d8b7704..0b6093d8 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "transformation", "plane", "2d", + "3d", "space", "geometry", "metric", From f629e33f5a345022f39903ecb2b4db6fd28747d3 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Fri, 28 Oct 2022 14:39:22 +0300 Subject: [PATCH 39/41] render docs --- docs/API.md | 1121 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 1041 insertions(+), 80 deletions(-) diff --git a/docs/API.md b/docs/API.md index a081956a..a663c969 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,5 +1,5 @@ -# Affineplane API Documentation v2.6.0 +# Affineplane API Documentation v2.7.0 Welcome to affineplane API reference documentation. These docs are generated with [yamdog](https://axelpale.github.io/yamdog/). @@ -27,14 +27,19 @@ The functions are grouped in the following submodules. - [affineplane.line2](#affineplaneline2) - [affineplane.line3](#affineplaneline3) - [affineplane.path2](#affineplanepath2) +- [affineplane.path3](#affineplanepath3) - [affineplane.plane2](#affineplaneplane2) - [affineplane.plane3](#affineplaneplane3) - [affineplane.point2](#affineplanepoint2) - [affineplane.point3](#affineplanepoint3) - [affineplane.poly2](#affineplanepoly2) +- [affineplane.quat4](#affineplanequat4) +- [affineplane.rect2](#affineplanerect2) +- [affineplane.rect3](#affineplanerect3) - [affineplane.size2](#affineplanesize2) - [affineplane.vec2](#affineplanevec2) - [affineplane.vec3](#affineplanevec3) +- [affineplane.vec4](#affineplanevec4) - [affineplane.vector2](#affineplanevector2) - [affineplane.vector3](#affineplanevector3) - [affineplane.version](#affineplaneversion) @@ -974,6 +979,8 @@ See [affineplane.plane3](#affineplaneplane3) for a positional variant. - [affineplane.helm3.toArray](#affineplanehelm3toarray) - [affineplane.helm3.toMatrix](#affineplanehelm3tomatrix) - [affineplane.helm3.transitFrom](#affineplanehelm3transitfrom) +- [affineplane.helm3.transitTo](#affineplanehelm3transitto) +- [affineplane.helm3.translateBy](#affineplanehelm3translateby) - [affineplane.helm3.validate](#affineplanehelm3validate) @@ -1938,6 +1945,8 @@ further by the given vector. - a [helm3](#affineplanehelm3), a transform +Aliases: [affineplane.helm3.translateBy](#affineplanehelm3translateby) + Source: [addTranslation.js](https://github.com/axelpale/affineplane/blob/main/lib/helm3/addTranslation.js) @@ -2504,6 +2513,37 @@ to the reference plane. Source: [transitFrom.js](https://github.com/axelpale/affineplane/blob/main/lib/helm3/transitFrom.js) + +## [affineplane](#affineplane).[helm3](#affineplanehelm3).[transitTo](#affineplanehelm3transitto)(tr, target) + +Transit a [helm3](#affineplanehelm3) to a target plane. +In other words, represent the [helm3](#affineplanehelm3) +in the coordinate system of the plane. + +

Parameters:

+ + +- *tr* + - a [helm3](#affineplanehelm3) transformation represented on the reference plane. +- *target* + - a [plane3](#affineplaneplane3), the target plane, represented on the reference plane. + + +

Returns:

+ + +- a [helm3](#affineplanehelm3), represented on the target plane. + + +Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/helm3/transitTo.js) + + +## [affineplane](#affineplane).[helm3](#affineplanehelm3).[translateBy](#affineplanehelm3translateby) + +Alias of [affineplane.helm3.addTranslation](#affineplanehelm3addtranslation) + +Source: [addTranslation.js](https://github.com/axelpale/affineplane/blob/main/lib/helm3/addTranslation.js) + ## [affineplane](#affineplane).[helm3](#affineplanehelm3).[validate](#affineplanehelm3validate)(tr) @@ -2756,11 +2796,56 @@ Does not form a polygon but a sequence of line segments.

Contents:

+- [affineplane.path2.combine](#affineplanepath2combine) - [affineplane.path2.create](#affineplanepath2create) +- [affineplane.path2.transitFrom](#affineplanepath2transitfrom) +- [affineplane.path2.transitTo](#affineplanepath2transitto) Source: [path2/index.js](https://github.com/axelpale/affineplane/blob/main/lib/path2/index.js) + +## [affineplane](#affineplane).[path3](#affineplanepath3) + +Three-dimensional path; Array of [point3](#affineplanepoint3); Open sequence of points; +Does not form a polygon but a sequence of line segments. +`[{ x, y, z }, { x, y, z }, ...]` + + +

Contents:

+ + +- [affineplane.path3.combine](#affineplanepath3combine) +- [affineplane.path3.create](#affineplanepath3create) +- [affineplane.path3.projectTo](#affineplanepath3projectto) +- [affineplane.path3.transitFrom](#affineplanepath3transitfrom) +- [affineplane.path3.transitTo](#affineplanepath3transitto) + + +Source: [path3/index.js](https://github.com/axelpale/affineplane/blob/main/lib/path3/index.js) + + +## [affineplane](#affineplane).[path2](#affineplanepath2).[combine](#affineplanepath2combine)(pa, pb) + +Combine paths together. + +

Parameters:

+ + +- *pa* + - a [path2](#affineplanepath2) +- *pb* + - a [path2](#affineplanepath2) + + +

Returns:

+ + +- a [path2](#affineplanepath2) + + +Source: [combine.js](https://github.com/axelpale/affineplane/blob/main/lib/path2/combine.js) + ## [affineplane](#affineplane).[path2](#affineplanepath2).[create](#affineplanepath2create)(points) @@ -2781,6 +2866,161 @@ Create a path on plane. Deep-clones the points array. Source: [create.js](https://github.com/axelpale/affineplane/blob/main/lib/path2/create.js) + +## [affineplane](#affineplane).[path2](#affineplanepath2).[transitFrom](#affineplanepath2transitfrom)(path, source) + +Represent a path on the reference basis. + +

Parameters:

+ + +- *path* + - a [path2](#affineplanepath2), represented on the source basis. +- *source* + - a [plane2](#affineplaneplane2), the source basis, represented on the reference basis. + + +

Returns:

+ + +- a [path2](#affineplanepath2), represented on the reference basis. + + +Source: [transitFrom.js](https://github.com/axelpale/affineplane/blob/main/lib/path2/transitFrom.js) + + +## [affineplane](#affineplane).[path2](#affineplanepath2).[transitTo](#affineplanepath2transitto)(path, target) + +Transit a path to the target basis. + +

Parameters:

+ + +- *path* + - a [path2](#affineplanepath2), on the reference basis. +- *target* + - a [plane2](#affineplaneplane2), the target basis, represented on the reference basis. + + +

Returns:

+ + +- a [path2](#affineplanepath2), represented on the target basis. + + +Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/path2/transitTo.js) + + +## [affineplane](#affineplane).[path3](#affineplanepath3).[combine](#affineplanepath3combine)(pa, pb) + +Combine paths together. + +

Parameters:

+ + +- *pa* + - a path3 +- *pb* + - a path3 + + +

Returns:

+ + +- a path3 + + +Source: [combine.js](https://github.com/axelpale/affineplane/blob/main/lib/path3/combine.js) + + +## [affineplane](#affineplane).[path3](#affineplanepath3).[create](#affineplanepath3create)(points) + +Create a path on plane. Deep-clones the points array. + +

Parameters:

+ + +- *points* + - array of [point3](#affineplanepoint3) + + +

Returns:

+ + +- a path3, array of points + + +Source: [create.js](https://github.com/axelpale/affineplane/blob/main/lib/path3/create.js) + + +## [affineplane](#affineplane).[path3](#affineplanepath3).[projectTo](#affineplanepath3projectto)(path, plane[, camera]) + +Project a 3D path onto a 2D plane orthogonally. +Project perspectively if a camera position is given. + +

Parameters:

+ + +- *path* + - a path3, in the reference basis. +- *plane* + - a [plane3](#affineplaneplane3), in the reference basis. The image plane to which to project. +- *camera* + - a [point3](#affineplanepoint3), in the reference basis. The location of the camera. + + +

Returns:

+ + +- a [path2](#affineplanepath2) on the image plane. + + +Source: [projectTo.js](https://github.com/axelpale/affineplane/blob/main/lib/path3/projectTo.js) + + +## [affineplane](#affineplane).[path3](#affineplanepath3).[transitFrom](#affineplanepath3transitfrom)(path, source) + +Represent a path on the reference basis. + +

Parameters:

+ + +- *path* + - a path3, represented on the source basis. +- *source* + - a [plane3](#affineplaneplane3), the source basis, represented on the reference basis. + + +

Returns:

+ + +- a path3, represented on the reference basis. + + +Source: [transitFrom.js](https://github.com/axelpale/affineplane/blob/main/lib/path3/transitFrom.js) + + +## [affineplane](#affineplane).[path3](#affineplanepath3).[transitTo](#affineplanepath3transitto)(path, target) + +Transit a path to the target basis. + +

Parameters:

+ + +- *path* + - a path3, on the reference basis. +- *target* + - a [plane3](#affineplaneplane3), the target basis, represented on the reference basis. + + +

Returns:

+ + +- a path3, represented on the target basis. + + +Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/path3/transitTo.js) + ## [affineplane](#affineplane).[plane2](#affineplaneplane2) @@ -3985,6 +4225,7 @@ translation of the plane on which they are represented. - [affineplane.point3.polarOffset](#affineplanepoint3polaroffset) - [affineplane.point3.projectTo](#affineplanepoint3projectto) - [affineplane.point3.rotateBy](#affineplanepoint3rotateby) +- [affineplane.point3.round](#affineplanepoint3round) - [affineplane.point3.toArray](#affineplanepoint3toarray) - [affineplane.point3.transitFrom](#affineplanepoint3transitfrom) - [affineplane.point3.transitTo](#affineplanepoint3transitto) @@ -4371,7 +4612,7 @@ Transform a point. Source: [transform.js](https://github.com/axelpale/affineplane/blob/main/lib/point2/transform.js) -## [affineplane](#affineplane).[point2](#affineplanepoint2).[transitFrom](#affineplanepoint2transitfrom)(point, plane) +## [affineplane](#affineplane).[point2](#affineplanepoint2).[transitFrom](#affineplanepoint2transitfrom)(point, source) Transit a [point2](#affineplanepoint2) from the source plane to the reference plane. @@ -4756,6 +4997,26 @@ Roll is applied before pitch. Source: [rotateBy.js](https://github.com/axelpale/affineplane/blob/main/lib/point3/rotateBy.js) + +## [affineplane](#affineplane).[point3](#affineplanepoint3).[round](#affineplanepoint3round)(p) + +Move point to the closest integer coordinate. + +

Parameters:

+ + +- *p* + - a [point3](#affineplanepoint3) + + +

Returns:

+ + +- a [point3](#affineplanepoint3), having integer coordinates. + + +Source: [round.js](https://github.com/axelpale/affineplane/blob/main/lib/point3/round.js) + ## [affineplane](#affineplane).[point3](#affineplanepoint3).[toArray](#affineplanepoint3toarray)(p) @@ -4910,173 +5171,726 @@ Create a polygon on plane. Deep-clones the points array. Source: [create.js](https://github.com/axelpale/affineplane/blob/main/lib/poly2/create.js) - -## [affineplane](#affineplane).[size2](#affineplanesize2) - -Two-dimensional rectangular size, consisting of width and height. + +## [affineplane](#affineplane).[quat4](#affineplanequat4) -Represented with an object `{ w, h }`. +A quaternion `{ a, b, c, d }`

Contents:

-- [affineplane.size2.area](#affineplanesize2area) -- [affineplane.size2.atNorm](#affineplanesize2atnorm) -- [affineplane.size2.create](#affineplanesize2create) -- [affineplane.size2.scaleBy](#affineplanesize2scaleby) -- [affineplane.size2.transitFrom](#affineplanesize2transitfrom) -- [affineplane.size2.transitTo](#affineplanesize2transitto) +- [affineplane.quat4.add](#affineplanequat4add) +- [affineplane.quat4.conjugate](#affineplanequat4conjugate) +- [affineplane.quat4.create](#affineplanequat4create) +- [affineplane.quat4.diff](#affineplanequat4diff) +- [affineplane.quat4.difference](#affineplanequat4difference) +- [affineplane.quat4.fromDirectionAndAngle](#affineplanequat4fromdirectionandangle) +- [affineplane.quat4.fromVector](#affineplanequat4fromvector) +- [affineplane.quat4.hamilton](#affineplanequat4hamilton) +- [affineplane.quat4.multiply](#affineplanequat4multiply) +- [affineplane.quat4.norm](#affineplanequat4norm) -Source: [size2/index.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/index.js) +Source: [quat4/index.js](https://github.com/axelpale/affineplane/blob/main/lib/quat4/index.js) - -## [affineplane](#affineplane).[size2](#affineplanesize2).[area](#affineplanesize2area)(sz) + +## [affineplane](#affineplane).[quat4](#affineplanequat4).[add](#affineplanequat4add)(q, p) -Area. If your w and h are in px, this gives you -the total number of pixels. +Add two quaternions together via component-wise addition.

Parameters:

-- *sz* - - a [size2](#affineplanesize2) +- *q* + - a quat4 +- *p* + - a quat4

Returns:

-- a number +- a quat4 -Source: [area.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/area.js) +Source: [add.js](https://github.com/axelpale/affineplane/blob/main/lib/quat4/add.js) - -## [affineplane](#affineplane).[size2](#affineplanesize2).[atNorm](#affineplanesize2atnorm)(sz, nw, nh) + +## [affineplane](#affineplane).[quat4](#affineplanequat4).[conjugate](#affineplanequat4conjugate)(q) -Find a point on the area. +Get the conjugate of a quaternion.

Parameters:

-- *sz* - - a [size2](#affineplanesize2) -- *nw* - - a normalized width in 0..1 -- *nh* - - a normalized height +- *q* + - a quat4

Returns:

-- a [point2](#affineplanepoint2) - +- a quat4 -Source: [atNorm.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/atNorm.js) - -## [affineplane](#affineplane).[size2](#affineplanesize2).[create](#affineplanesize2create)(width, height) +Source: [conjugate.js](https://github.com/axelpale/affineplane/blob/main/lib/quat4/conjugate.js) -Create a [size2](#affineplanesize2) object. + +## [affineplane](#affineplane).[quat4](#affineplanequat4).[create](#affineplanequat4create)(a, b, c, d)

Parameters:

-- *width* +- *a* - a number -- *height* +- *b* + - a number +- *c* + - a number +- *d* - a number

Returns:

-- a [size2](#affineplanesize2) +- a quat4 -Source: [create.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/create.js) +Source: [create.js](https://github.com/axelpale/affineplane/blob/main/lib/quat4/create.js) - -## [affineplane](#affineplane).[size2](#affineplanesize2).[scaleBy](#affineplanesize2scaleby)(sz, multiplier) + +## [affineplane](#affineplane).[quat4](#affineplanequat4).[diff](#affineplanequat4diff) -Ratio-preserving scale. Multiplies all dimensions uniformly. +Alias of [affineplane.quat4.difference](#affineplanequat4difference) + +Source: [difference.js](https://github.com/axelpale/affineplane/blob/main/lib/quat4/difference.js) + + +## [affineplane](#affineplane).[quat4](#affineplanequat4).[difference](#affineplanequat4difference)(q, p) + +Get the quaternion q - p.

Parameters:

-- *sz* - - a [size2](#affineplanesize2) -- *multiplier* - - a number +- *q* + - a quat4 +- *p* + - a quat4

Returns:

-- a [size2](#affineplanesize2) +- a quat4 -Source: [scaleBy.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/scaleBy.js) +Aliases: [affineplane.quat4.diff](#affineplanequat4diff) - -## [affineplane](#affineplane).[size2](#affineplanesize2).[transitFrom](#affineplanesize2transitfrom)(size, source) +Source: [difference.js](https://github.com/axelpale/affineplane/blob/main/lib/quat4/difference.js) -Transit a size from the source plane -to the reference plane. + +## [affineplane](#affineplane).[quat4](#affineplanequat4).[fromDirectionAndAngle](#affineplanequat4fromdirectionandangle)(dir, angle) + +Construct a unit quaternion from an axis vector and a rotation angle +around that direction. Note that if this quaternion is later used to +rotate a 3D vector, the rotation angle will be applied twice to map +the vector back to 3D.

Parameters:

-- *size* - - a [size2](#affineplanesize2) on the source plane. -- *source* - - a [plane2](#affineplaneplane2), the source plane, represented on the reference plane. +- *dir* + - a dir3 or a [vec3](#affineplanevec3) +- *angle* + - a number, an angle in radians

Returns:

-- a [size2](#affineplanesize2), represented on the reference plane. +- a quat4 -Source: [transitFrom.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/transitFrom.js) +Source: [fromAxisAngle.js](https://github.com/axelpale/affineplane/blob/main/lib/quat4/fromAxisAngle.js) - -## [affineplane](#affineplane).[size2](#affineplanesize2).[transitTo](#affineplanesize2transitto)(size, target) + +## [affineplane](#affineplane).[quat4](#affineplanequat4).[fromVector](#affineplanequat4fromvector)(vec) -Transit a [size2](#affineplanesize2) to a target plane. -In other words, represent the size -in the coordinate system of the target plane. +Construct a vector quaternion i.e. a quaternion with zero scalar part.

Parameters:

-- *size* - - a [size2](#affineplanesize2) on the reference plane. -- *target* - - a [plane2](#affineplaneplane2), the target plane, represented on the reference plane. +- *vec* + - a [vec3](#affineplanevec3)

Returns:

-- a [size2](#affineplanesize2) on the target plane. +- a quat4 -Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/transitTo.js) +Source: [fromVector.js](https://github.com/axelpale/affineplane/blob/main/lib/quat4/fromVector.js) - -## [affineplane](#affineplane).[vec2](#affineplanevec2) + +## [affineplane](#affineplane).[quat4](#affineplanequat4).[hamilton](#affineplanequat4hamilton)(q, p) -Vector is a two dimensional dynamic movent between points, -also known as a displacement vector. See [affineplane.point2](#affineplanepoint2) for -position vectors. +The Hamilton product of two quaternions. -![A vector](geometry_vector.png) +

Parameters:

-Aliases: [affineplane.vector2](#affineplanevector2) + +- *q* + - a quat4 +- *p* + - a quat4 + + +

Returns:

+ + +- a quat4 + + +Aliases: [affineplane.quat4.multiply](#affineplanequat4multiply) + +Source: [hamilton.js](https://github.com/axelpale/affineplane/blob/main/lib/quat4/hamilton.js) + + +## [affineplane](#affineplane).[quat4](#affineplanequat4).[multiply](#affineplanequat4multiply) + +Alias of [affineplane.quat4.hamilton](#affineplanequat4hamilton) + +Source: [hamilton.js](https://github.com/axelpale/affineplane/blob/main/lib/quat4/hamilton.js) + + +## [affineplane](#affineplane).[quat4](#affineplanequat4).[norm](#affineplanequat4norm)(q) + +The length of quaternion. Also called the norm. + +

Parameters:

+ + +- *q* + - a quat4 + + +

Returns:

+ + +- a number + + +Source: [norm.js](https://github.com/axelpale/affineplane/blob/main/lib/quat4/norm.js) + + +## [affineplane](#affineplane).[rect2](#affineplanerect2) + +Rectangle on a two-dimensional plane. A rectangle is an object +`{ basis: { a, b, x, y }, size: { w, h } }`, where the basis +is a transformation from rectangle's inner coordinate system +to the reference coordinate system. + + +

Contents:

+ + +- [affineplane.rect2.at](#affineplanerect2at) +- [affineplane.rect2.atNorm](#affineplanerect2atnorm) +- [affineplane.rect2.getArea](#affineplanerect2getarea) +- [affineplane.rect2.getArea](#affineplanerect2getarea) +- [affineplane.rect2.getBounds](#affineplanerect2getbounds) +- [affineplane.rect2.getBounds](#affineplanerect2getbounds) +- [affineplane.rect2.transitFrom](#affineplanerect2transitfrom) +- [affineplane.rect2.transitTo](#affineplanerect2transitto) + + +Source: [rect2/index.js](https://github.com/axelpale/affineplane/blob/main/lib/rect2/index.js) + + +## [affineplane](#affineplane).[rect3](#affineplanerect3) + +A rectangle, floating in three dimensions. A rect3 is an object +`{ basis: { a, b, x, y, z }, size: { w, h } }`, where the basis +is a [helm3](#affineplanehelm3) - a Helmert transformation from rectangle's inner coordinate +system to the reference coordinate system. + + +

Contents:

+ + +- [affineplane.rect3.at](#affineplanerect3at) +- [affineplane.rect3.atNorm](#affineplanerect3atnorm) +- [affineplane.rect3.create](#affineplanerect3create) +- [affineplane.rect3.transitFrom](#affineplanerect3transitfrom) +- [affineplane.rect3.transitTo](#affineplanerect3transitto) + + +Source: [rect3/index.js](https://github.com/axelpale/affineplane/blob/main/lib/rect3/index.js) + + +## [affineplane](#affineplane).[rect2](#affineplanerect2).[at](#affineplanerect2at)(rect, rx, ry) + +Take a point on the rect, represented on the rects outer basis. + +

Parameters:

+ + +- *rect* + - a rect2 +- *rx* + - horizontal distance from the top-left corner of the rectangle represented on the rects inner basis +- *ry* + - vertical distance from the top-left corner of the rectangle represented on the rects inner basis + + +

Returns:

+ + +- a [point2](#affineplanepoint2) on the outer basis + + +Source: [at.js](https://github.com/axelpale/affineplane/blob/main/lib/rect2/at.js) + + +## [affineplane](#affineplane).[rect2](#affineplanerect2).[atNorm](#affineplanerect2atnorm)(rect, nw, nh) + +Take a point on the rect. + +

Parameters:

+ + +- *rect* + - a rectangle +- *nw* + - a number, a normalized coordinate along width 0..1 +- *nh* + - a number, a normalized coordinate along height 0..1 + + +

Returns:

+ + +- a point on the rectangle's outer basis + + +Source: [atNorm.js](https://github.com/axelpale/affineplane/blob/main/lib/rect2/atNorm.js) + + +## [affineplane](#affineplane).[rect2](#affineplanerect2).[getArea](#affineplanerect2getarea)(rect) + +Compute rectangle area. This returns the area on the reference basis. + +

Parameters:

+ + +- *rect* + - a rect2, on the reference basis. + + +

Returns:

+ + +- a number, the area on the reference basis. + + +Source: [getArea.js](https://github.com/axelpale/affineplane/blob/main/lib/rect2/getArea.js) + + +## [affineplane](#affineplane).[rect2](#affineplanerect2).[getArea](#affineplanerect2getarea)(rect) + +Compute rectangle area. This returns the area on the reference basis. + +

Parameters:

+ + +- *rect* + - a rect2, on the reference basis. + + +

Returns:

+ + +- a number, the area on the reference basis. + + +Source: [getArea.js](https://github.com/axelpale/affineplane/blob/main/lib/rect3/getArea.js) + + +## [affineplane](#affineplane).[rect2](#affineplanerect2).[getBounds](#affineplanerect2getbounds)(rect) + +Get outer bounds of the given rect. In other words, +return a rectangle with no rotation and no dilation with respect to +the reference basis but has translation and size so that it covers +the given rectangle. + +

Parameters:

+ + +- *rect* + - a rect2, on the reference basis. + + +

Returns:

+ + +- a rect2, on the reference basis. + + +Source: [getBounds.js](https://github.com/axelpale/affineplane/blob/main/lib/rect2/getBounds.js) + + +## [affineplane](#affineplane).[rect2](#affineplanerect2).[getBounds](#affineplanerect2getbounds)(rect) + +Get outer bounds of the given rect. In other words, +return a rectangle with no rotation and no dilation with respect to +the reference basis but has translation and size so that it covers +the given rectangle. + +

Parameters:

+ + +- *rect* + - a rect2, on the reference basis. + + +

Returns:

+ + +- a rect2, on the reference basis. + + +Source: [getBounds.js](https://github.com/axelpale/affineplane/blob/main/lib/rect3/getBounds.js) + + +## [affineplane](#affineplane).[rect2](#affineplanerect2).[transitFrom](#affineplanerect2transitfrom)(rect, source) + +Convert a rectangle from source basis to the reference basis. + +

Parameters:

+ + +- *rect* + - a rect2, a rectangle on the source basis. +- *source* + - a [plane2](#affineplaneplane2), the source basis represented on the reference basis. + + +

Returns:

+ + +- a rect2, represented on the reference basis. + + +Source: [transitFrom.js](https://github.com/axelpale/affineplane/blob/main/lib/rect2/transitFrom.js) + + +## [affineplane](#affineplane).[rect2](#affineplanerect2).[transitTo](#affineplanerect2transitto)(rect, target) + +Convert a rectangle from the reference basis to the target basis. + +

Parameters:

+ + +- *rect* + - a rectangle on the reference basis. +- *target* + - a [plane2](#affineplaneplane2), the target basis represented on the reference basis. + + +

Returns:

+ + +- a rect2, represented on the target basis. + + +Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/rect2/transitTo.js) + + +## [affineplane](#affineplane).[rect3](#affineplanerect3).[at](#affineplanerect3at)(rect, rx, ry) + +Take a point on the rect, represented on the rects outer basis. + +

Parameters:

+ + +- *rect* + - a rect2 +- *rx* + - horizontal distance from the top-left corner of the rectangle represented on the rects inner basis +- *ry* + - vertical distance from the top-left corner of the rectangle represented on the rects inner basis + + +

Returns:

+ + +- a [point3](#affineplanepoint3), represented on the outer basis. + + +Source: [at.js](https://github.com/axelpale/affineplane/blob/main/lib/rect3/at.js) + + +## [affineplane](#affineplane).[rect3](#affineplanerect3).[atNorm](#affineplanerect3atnorm)(rect, nw, nh) + +Take a point on the rect. + +

Parameters:

+ + +- *rect* + - a rectangle +- *nw* + - a number, a normalized coordinate along width 0..1 +- *nh* + - a number, a normalized coordinate along height 0..1 + + +

Returns:

+ + +- a [point3](#affineplanepoint3), in the rectangle's outer basis + + +Source: [atNorm.js](https://github.com/axelpale/affineplane/blob/main/lib/rect3/atNorm.js) + + +## [affineplane](#affineplane).[rect3](#affineplanerect3).[create](#affineplanerect3create)(basis, size) + +Construct a rect3 object from basis and size. + +

Parameters:

+ + +- *basis* + - a basis3 +- *size* + - a [size2](#affineplanesize2) + + +

Returns:

+ + +- a rect3 + + +Source: [create.js](https://github.com/axelpale/affineplane/blob/main/lib/rect3/create.js) + + +## [affineplane](#affineplane).[rect3](#affineplanerect3).[transitFrom](#affineplanerect3transitfrom)(rect, source) + +Convert a rectangle from source basis to the reference basis. + +

Parameters:

+ + +- *rect* + - a rect3, a rectangle on the source basis. +- *source* + - a basis3, the source basis represented on the reference basis. + + +

Returns:

+ + +- a rect3, represented on the reference basis. + + +Source: [transitFrom.js](https://github.com/axelpale/affineplane/blob/main/lib/rect3/transitFrom.js) + + +## [affineplane](#affineplane).[rect3](#affineplanerect3).[transitTo](#affineplanerect3transitto)(rect, target) + +Convert a rectangle from the reference basis to the target basis. + +

Parameters:

+ + +- *rect* + - a rectangle on the reference basis. +- *target* + - a basis3, the target basis represented on the reference basis. + + +

Returns:

+ + +- a rect3, represented on the target basis. + + +Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/rect3/transitTo.js) + + +## [affineplane](#affineplane).[size2](#affineplanesize2) + +Two-dimensional rectangular size, consisting of width and height. + +Represented with an object `{ w, h }`. + + +

Contents:

+ + +- [affineplane.size2.area](#affineplanesize2area) +- [affineplane.size2.atNorm](#affineplanesize2atnorm) +- [affineplane.size2.create](#affineplanesize2create) +- [affineplane.size2.scaleBy](#affineplanesize2scaleby) +- [affineplane.size2.transitFrom](#affineplanesize2transitfrom) +- [affineplane.size2.transitTo](#affineplanesize2transitto) + + +Source: [size2/index.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/index.js) + + +## [affineplane](#affineplane).[size2](#affineplanesize2).[area](#affineplanesize2area)(sz) + +Area. If your w and h are in px, this gives you +the total number of pixels. + +

Parameters:

+ + +- *sz* + - a [size2](#affineplanesize2) + + +

Returns:

+ + +- a number + + +Source: [area.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/area.js) + + +## [affineplane](#affineplane).[size2](#affineplanesize2).[atNorm](#affineplanesize2atnorm)(sz, nw, nh) + +Find a point on the area. + +

Parameters:

+ + +- *sz* + - a [size2](#affineplanesize2) +- *nw* + - a normalized width in 0..1 +- *nh* + - a normalized height + + +

Returns:

+ + +- a [point2](#affineplanepoint2) + + +Source: [atNorm.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/atNorm.js) + + +## [affineplane](#affineplane).[size2](#affineplanesize2).[create](#affineplanesize2create)(width, height) + +Create a [size2](#affineplanesize2) object. + +

Parameters:

+ + +- *width* + - a number +- *height* + - a number + + +

Returns:

+ + +- a [size2](#affineplanesize2) + + +Source: [create.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/create.js) + + +## [affineplane](#affineplane).[size2](#affineplanesize2).[scaleBy](#affineplanesize2scaleby)(sz, multiplier) + +Ratio-preserving scale. Multiplies all dimensions uniformly. + +

Parameters:

+ + +- *sz* + - a [size2](#affineplanesize2) +- *multiplier* + - a number + + +

Returns:

+ + +- a [size2](#affineplanesize2) + + +Source: [scaleBy.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/scaleBy.js) + + +## [affineplane](#affineplane).[size2](#affineplanesize2).[transitFrom](#affineplanesize2transitfrom)(size, source) + +Transit a size from the source plane +to the reference plane. + +

Parameters:

+ + +- *size* + - a [size2](#affineplanesize2) on the source plane. +- *source* + - a [plane2](#affineplaneplane2), the source plane, represented on the reference plane. + + +

Returns:

+ + +- a [size2](#affineplanesize2), represented on the reference plane. + + +Source: [transitFrom.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/transitFrom.js) + + +## [affineplane](#affineplane).[size2](#affineplanesize2).[transitTo](#affineplanesize2transitto)(size, target) + +Transit a [size2](#affineplanesize2) to a target plane. +In other words, represent the size +in the coordinate system of the target plane. + +

Parameters:

+ + +- *size* + - a [size2](#affineplanesize2) on the reference plane. +- *target* + - a [plane2](#affineplaneplane2), the target plane, represented on the reference plane. + + +

Returns:

+ + +- a [size2](#affineplanesize2) on the target plane. + + +Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/size2/transitTo.js) + + +## [affineplane](#affineplane).[vec2](#affineplanevec2) + +Vector is a two dimensional dynamic movent between points, +also known as a displacement vector. See [affineplane.point2](#affineplanepoint2) for +position vectors. + +![A vector](geometry_vector.png) + +Aliases: [affineplane.vector2](#affineplanevector2)

Contents:

@@ -5152,8 +5966,10 @@ Aliases: [affineplane.vector3](#affineplanevector3) - [affineplane.vec3.invert](#affineplanevec3invert) - [affineplane.vec3.magnitude](#affineplanevec3magnitude) - [affineplane.vec3.negate](#affineplanevec3negate) +- [affineplane.vec3.norm](#affineplanevec3norm) - [affineplane.vec3.normalize](#affineplanevec3normalize) - [affineplane.vec3.projectTo](#affineplanevec3projectto) +- [affineplane.vec3.rotateAroundAxis](#affineplanevec3rotatearoundaxis) - [affineplane.vec3.rotateBy](#affineplanevec3rotateby) - [affineplane.vec3.rotateBy](#affineplanevec3rotateby) - [affineplane.vec3.scaleBy](#affineplanevec3scaleby) @@ -5172,6 +5988,23 @@ Aliases: [affineplane.vector3](#affineplanevector3) Source: [vec3/index.js](https://github.com/axelpale/affineplane/blob/main/lib/vec3/index.js) + +## [affineplane](#affineplane).[vec4](#affineplanevec4) + +A vec4 is a 4D vector { x, y, z, w }. + + +

Contents:

+ + +- [affineplane.vec4.add](#affineplanevec4add) +- [affineplane.vec4.create](#affineplanevec4create) +- [affineplane.vec4.norm](#affineplanevec4norm) +- [affineplane.vec4.scaleBy](#affineplanevec4scaleby) + + +Source: [vec4/index.js](https://github.com/axelpale/affineplane/blob/main/lib/vec4/index.js) + ## [affineplane](#affineplane).[vec2](#affineplanevec2).[add](#affineplanevec2add)(v, w) @@ -6254,6 +7087,8 @@ The euclidean length of the vector. - a number +Aliases: [affineplane.vec3.norm](#affineplanevec3norm) + Source: [magnitude.js](https://github.com/axelpale/affineplane/blob/main/lib/vec3/magnitude.js) @@ -6263,6 +7098,13 @@ Alias of [affineplane.vec3.invert](#affineplanevec3invert) Source: [invert.js](https://github.com/axelpale/affineplane/blob/main/lib/vec3/invert.js) + +## [affineplane](#affineplane).[vec3](#affineplanevec3).[norm](#affineplanevec3norm) + +Alias of [affineplane.vec3.magnitude](#affineplanevec3magnitude) + +Source: [magnitude.js](https://github.com/axelpale/affineplane/blob/main/lib/vec3/magnitude.js) + ## [affineplane](#affineplane).[vec3](#affineplanevec3).[normalize](#affineplanevec3normalize) @@ -6278,14 +7120,16 @@ We cannot project 3D vectors perspectively because they have no fixed position and the perspective position depends on the position. See [affineplane.line3](#affineplaneline3).projectTo for perspective projection of a vector with fixed position. +See [affineplane.path3.projectTo](#affineplanepath3projectto) for perspective projection of +a sequence of points.

Parameters:

- *vec* - - a [vec3](#affineplanevec3) in the reference space. + - a [vec3](#affineplanevec3), in the reference basis. - *plane* - - a [plane3](#affineplaneplane3) in the reference space. The image plane. + - a [plane3](#affineplaneplane3), in the reference basis. The image plane to which to project.

Returns:

@@ -6296,6 +7140,35 @@ for perspective projection of a vector with fixed position. Source: [projectTo.js](https://github.com/axelpale/affineplane/blob/main/lib/vec3/projectTo.js) + +## [affineplane](#affineplane).[vec3](#affineplanevec3).[rotateAroundAxis](#affineplanevec3rotatearoundaxis)(v, axis, angle) + +Rotate a vector by the given radian angle around the specified axis vector. +The method uses [Rodriques' rotation formula]( +https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula) under +the hood. If you need to rotate multiple vectors simultaneously, +it is probably better to construct a rotation matrix first and apply +it to each vector. + +

Parameters:

+ + +- *v* + - a [vec3](#affineplanevec3) +- *axis* + - a [vec3](#affineplanevec3), must not be zero vector +- *angle* + - a number, an angle in radians. Right-hand rotation around the given axis. + + +

Returns:

+ + +- a [vec3](#affineplanevec3) + + +Source: [rotateAroundAxis.js](https://github.com/axelpale/affineplane/blob/main/lib/vec3/rotateAroundAxis.js) + ## [affineplane](#affineplane).[vec3](#affineplanevec3).[rotateBy](#affineplanevec3rotateby)(v, roll[, pitch]) @@ -6609,6 +7482,94 @@ Check if object is a valid [vec3](#affineplanevec3). Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/vec3/validate.js) + +## [affineplane](#affineplane).[vec4](#affineplanevec4).[add](#affineplanevec4add)(v, w) + +Add two vectors together via component-wise addition. + +

Parameters:

+ + +- *v* + - a vec4 +- *w* + - a vec4 + + +

Returns:

+ + +- a vec4 + + +Source: [add.js](https://github.com/axelpale/affineplane/blob/main/lib/vec4/add.js) + + +## [affineplane](#affineplane).[vec4](#affineplanevec4).[create](#affineplanevec4create)(x, y, z, w) + +

Parameters:

+ + +- *x* + - a number +- *y* + - a number +- *z* + - a number +- *w* + - a number + + +

Returns:

+ + +- a vec4 + + +Source: [create.js](https://github.com/axelpale/affineplane/blob/main/lib/vec4/create.js) + + +## [affineplane](#affineplane).[vec4](#affineplanevec4).[norm](#affineplanevec4norm)(v) + +The length of 4D vector. Also called the norm. + +

Parameters:

+ + +- *v* + - a vec4 + + +

Returns:

+ + +- a number + + +Source: [norm.js](https://github.com/axelpale/affineplane/blob/main/lib/vec4/norm.js) + + +## [affineplane](#affineplane).[vec4](#affineplanevec4).[scaleBy](#affineplanevec4scaleby)(v, m) + +Scalar multiplication of a vector. + +

Parameters:

+ + +- *v* + - a vec4 +- *m* + - a number, a multiplier + + +

Returns:

+ + +- a vec4 + + +Source: [scaleBy.js](https://github.com/axelpale/affineplane/blob/main/lib/vec4/scaleBy.js) + ## [affineplane](#affineplane).[vector2](#affineplanevector2) From c41f0871ff00026a3eab5ae69c3941c2132083b1 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Fri, 28 Oct 2022 14:40:06 +0300 Subject: [PATCH 40/41] bump v2.7 --- lib/version.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/version.js b/lib/version.js index d1d5b459..afd8deeb 100644 --- a/lib/version.js +++ b/lib/version.js @@ -1,2 +1,2 @@ // Generated by genversion. -module.exports = '2.6.0' +module.exports = '2.7.0' diff --git a/package.json b/package.json index 0b6093d8..dbfaa849 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "affineplane", - "version": "2.6.0", + "version": "2.7.0", "description": "Affine plane geometry library", "keywords": [ "affine", From bf79e70272bb89cef363d4fcd49a74cfd573655e Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Fri, 28 Oct 2022 14:50:58 +0300 Subject: [PATCH 41/41] set travis build env to ubuntu 20.04 focal fossa --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ad6dc084..e69c3bdd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: node_js +dist: focal node_js: - 10 - 12