From 6d32cbbe63a32149f2fc7b2b6944ef7d3085c5ec Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Mon, 6 Mar 2023 17:23:30 +0200 Subject: [PATCH 01/36] implement box2.hasPoint --- lib/box2/hasPoint.js | 33 +++++++++++++++++++++++++++++++++ lib/box2/index.js | 1 + test/box2/hasPoint.test.js | 21 +++++++++++++++++++++ test/box2/index.test.js | 1 + 4 files changed, 56 insertions(+) create mode 100644 lib/box2/hasPoint.js create mode 100644 test/box2/hasPoint.test.js diff --git a/lib/box2/hasPoint.js b/lib/box2/hasPoint.js new file mode 100644 index 00000000..453d2583 --- /dev/null +++ b/lib/box2/hasPoint.js @@ -0,0 +1,33 @@ +const getBasisInverse = require('./getBasisInverse') +const pointTransitFrom = require('../point2/transitFrom') + +module.exports = function (box, point) { + // @affineplane.box2.hasPoint() + // + // Test if a point is inside the box. If the point is at the box edge, + // it is counted as being inside. + // + // Parameters: + // box + // a box2, in the reference basis. + // point + // a point2, in the reference basis. + // + // Return: + // a boolean + // + + // Note: we thought it would be practical to count inner box (0,0) inside + // and inner box (w,h) outside. However, due to rotation that makes things + // confusing. Therefore all edge points are counted as inside. + + // Transit the point to the inner box basis for easier checking. + const innerBasis = getBasisInverse(box) + const innerPoint = pointTransitFrom(point, innerBasis) + + // Test the point + const px = innerPoint.x + const py = innerPoint.y + + return px >= 0 && px <= box.w && py >= 0 && py <= box.h +} diff --git a/lib/box2/index.js b/lib/box2/index.js index 16f8a48d..681e8bb3 100644 --- a/lib/box2/index.js +++ b/lib/box2/index.js @@ -25,6 +25,7 @@ exports.getPath = require('./getPath') exports.getPoints = exports.getPath exports.getPolygon = exports.getPath exports.getSize = require('./getSize') +exports.hasPoint = require('./hasPoint') exports.homothety = require('./homothety') exports.projectToPlane = require('./projectToPlane') exports.projectTo = exports.projectToPlane diff --git a/test/box2/hasPoint.test.js b/test/box2/hasPoint.test.js new file mode 100644 index 00000000..aaca4f08 --- /dev/null +++ b/test/box2/hasPoint.test.js @@ -0,0 +1,21 @@ +const box2 = require('../../lib/box2') + +module.exports = (ts) => { + ts.test('case: basic hasPoint', (t) => { + const box = { a: 1, b: 0, x: 1, y: 1, w: 2, h: 2 } + t.false(box2.hasPoint(box, { x: 0, y: 0 }), 'outside') + t.true(box2.hasPoint(box, { x: 1, y: 1 }), 'inside at edge') + t.true(box2.hasPoint(box, { x: 2, y: 2 }), 'inside') + t.true(box2.hasPoint(box, { x: 3, y: 3 }), 'outside at edge') + t.false(box2.hasPoint(box, { x: 3, y: 4 }), 'outside beyond') + + const robox = { a: 0, b: 1, x: 3, y: 1, w: 2, h: 2 } + t.false(box2.hasPoint(robox, { x: 0, y: 0 }), 'rotated, outside') + t.true(box2.hasPoint(robox, { x: 1, y: 1 }), 'rotated, inside at edge') + t.true(box2.hasPoint(robox, { x: 2, y: 2 }), 'rotated, inside') + t.true(box2.hasPoint(robox, { x: 3, y: 3 }), 'rotated, at LB corner') + t.false(box2.hasPoint(robox, { x: 3, y: 4 }), 'rotated, outside LB') + + t.end() + }) +} diff --git a/test/box2/index.test.js b/test/box2/index.test.js index f3a3913e..202218b3 100644 --- a/test/box2/index.test.js +++ b/test/box2/index.test.js @@ -14,6 +14,7 @@ const units = { getMinimumBounds: require('./getMinimumBounds.test'), getPath: require('./getPath.test'), getSize: require('./getSize.test'), + hasPoint: require('./hasPoint.test'), homothety: require('./homothety.test'), projectToPlane: require('./projectToPlane.test'), resizeBy: require('./resizeBy.test'), From 81c72f8f9066fd896c93d621c6fcf758f264fa98 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Mon, 6 Mar 2023 17:33:02 +0200 Subject: [PATCH 02/36] implement box3.getBasisInverse --- lib/box3/getBasisInverse.js | 23 +++++++++++++++++++++++ lib/box3/index.js | 1 + test/box3/getBasisInverse.test.js | 22 ++++++++++++++++++++++ test/box3/index.test.js | 1 + 4 files changed, 47 insertions(+) create mode 100644 lib/box3/getBasisInverse.js create mode 100644 test/box3/getBasisInverse.test.js diff --git a/lib/box3/getBasisInverse.js b/lib/box3/getBasisInverse.js new file mode 100644 index 00000000..3b37e0eb --- /dev/null +++ b/lib/box3/getBasisInverse.js @@ -0,0 +1,23 @@ +module.exports = (box) => { + // @affineplane.box3.getBasisInverse(box) + // + // Get the outer basis of the box represented in the box basis. + // The scale of the resulting basis is always 1. + // See also affineplane.box3.getBasis. + // + // Parameters: + // box + // a box3 in the reference basis. + // + // Return + // a plane3 in the box basis. The outer basis. + // + + return { + a: box.a, + b: -box.b, + x: -box.a * box.x - box.b * box.y, + y: box.b * box.x - box.a * box.y, + z: -box.z + } +} diff --git a/lib/box3/index.js b/lib/box3/index.js index 58737053..6f7c782b 100644 --- a/lib/box3/index.js +++ b/lib/box3/index.js @@ -17,6 +17,7 @@ exports.create = require('./create') exports.fromPoints = require('./fromPoints') exports.getAngle = require('./getAngle') exports.getBasis = require('./getBasis') +exports.getBasisInverse = require('./getBasisInverse') exports.getBounds = require('./getBounds') exports.getMinimumBounds = require('./getMinimumBounds') exports.getSize = require('./getSize') diff --git a/test/box3/getBasisInverse.test.js b/test/box3/getBasisInverse.test.js new file mode 100644 index 00000000..114ea50b --- /dev/null +++ b/test/box3/getBasisInverse.test.js @@ -0,0 +1,22 @@ +const affineplane = require('../../index') +const box3 = affineplane.box3 + +module.exports = (ts) => { + ts.test('case: basic getBasisInverse', (t) => { + const b = { a: 1, b: 0, x: 1, y: 2, z: 3, w: 3, h: 4, d: 5 } + t.almostEqualBasis( + box3.getBasisInverse(b), + { a: 1, b: 0, x: -1, y: -2, z: -3 }, + 'should do negated translation' + ) + + const bb = { a: 0, b: 1, x: 1, y: 2, z: 3, w: 3, h: 4, d: 5 } + t.almostEqualBasis( + box3.getBasisInverse(bb), + { a: 0, b: -1, x: -2, y: 1, z: -3 }, + 'should do inverted rotation' + ) + + t.end() + }) +} diff --git a/test/box3/index.test.js b/test/box3/index.test.js index 729d2d3e..b6b1db1d 100644 --- a/test/box3/index.test.js +++ b/test/box3/index.test.js @@ -8,6 +8,7 @@ const units = { fromPoints: require('./fromPoints.test'), getAngle: require('./getAngle.test'), getBasis: require('./getBasis.test'), + getBasisInverse: require('./getBasisInverse.test'), getBounds: require('./getBounds.test'), getMinimumBounds: require('./getMinimumBounds.test'), getSize: require('./getSize.test'), From 4ef99579788d462bee3e5cdb197dc735b085a4a3 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Mon, 6 Mar 2023 17:33:12 +0200 Subject: [PATCH 03/36] correct box2.hasPoint docs --- lib/box2/hasPoint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/box2/hasPoint.js b/lib/box2/hasPoint.js index 453d2583..78aad89a 100644 --- a/lib/box2/hasPoint.js +++ b/lib/box2/hasPoint.js @@ -2,7 +2,7 @@ const getBasisInverse = require('./getBasisInverse') const pointTransitFrom = require('../point2/transitFrom') module.exports = function (box, point) { - // @affineplane.box2.hasPoint() + // @affineplane.box2.hasPoint(box, point) // // Test if a point is inside the box. If the point is at the box edge, // it is counted as being inside. From 3236e0db75e649d4ff644713a13e474c482a3355 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Mon, 6 Mar 2023 17:33:59 +0200 Subject: [PATCH 04/36] implement box3.hasPoint --- lib/box3/hasPoint.js | 32 ++++++++++++++++++++++++++++++++ lib/box3/index.js | 1 + test/box3/hasPoint.test.js | 21 +++++++++++++++++++++ test/box3/index.test.js | 1 + 4 files changed, 55 insertions(+) create mode 100644 lib/box3/hasPoint.js create mode 100644 test/box3/hasPoint.test.js diff --git a/lib/box3/hasPoint.js b/lib/box3/hasPoint.js new file mode 100644 index 00000000..33a209b5 --- /dev/null +++ b/lib/box3/hasPoint.js @@ -0,0 +1,32 @@ +const getBasisInverse = require('./getBasisInverse') +const pointTransitFrom = require('../point3/transitFrom') + +module.exports = function (box, point) { + // @affineplane.box3.hasPoint(box, point) + // + // Test if a point is inside the box. If the point is at the box edge, + // it is counted as being inside. + // + // Parameters: + // box + // a box3, in the reference basis. + // point + // a point3, in the reference basis. + // + // Return: + // a boolean + // + + // Transit the point to the inner box basis for easier checking. + const innerBasis = getBasisInverse(box) + const innerPoint = pointTransitFrom(point, innerBasis) + + // Test the point + const px = innerPoint.x + const py = innerPoint.y + const pz = innerPoint.z + + return (px >= 0 && px <= box.w && + py >= 0 && py <= box.h && + pz >= 0 && pz <= box.d) +} diff --git a/lib/box3/index.js b/lib/box3/index.js index 6f7c782b..dee88fe5 100644 --- a/lib/box3/index.js +++ b/lib/box3/index.js @@ -22,6 +22,7 @@ exports.getBounds = require('./getBounds') exports.getMinimumBounds = require('./getMinimumBounds') exports.getSize = require('./getSize') exports.getVolume = require('./getVolume') +exports.hasPoint = require('./hasPoint') exports.homothety = require('./homothety') exports.projectToPlane = require('./projectToPlane') exports.projectTo = exports.projectToPlane diff --git a/test/box3/hasPoint.test.js b/test/box3/hasPoint.test.js new file mode 100644 index 00000000..4de9cafd --- /dev/null +++ b/test/box3/hasPoint.test.js @@ -0,0 +1,21 @@ +const box3 = require('../../lib/box3') + +module.exports = (ts) => { + ts.test('case: basic hasPoint', (t) => { + const box = { a: 1, b: 0, x: 1, y: 1, z: 1, w: 2, h: 2, d: 2 } + t.false(box3.hasPoint(box, { x: 0, y: 0, z: 0 }), 'outside') + t.true(box3.hasPoint(box, { x: 1, y: 1, z: 1 }), 'inside at edge') + t.true(box3.hasPoint(box, { x: 2, y: 2, z: 2 }), 'inside') + t.true(box3.hasPoint(box, { x: 3, y: 3, z: 3 }), 'outside at edge') + t.false(box3.hasPoint(box, { x: 3, y: 4, z: 3 }), 'outside beyond') + + const robox = { a: 0, b: 1, x: 3, y: 1, z: 1, w: 2, h: 2, d: 2 } + t.false(box3.hasPoint(robox, { x: 0, y: 0, z: 0 }), 'rotated, outside') + t.true(box3.hasPoint(robox, { x: 1, y: 1, z: 1 }), 'rotated, inside at edge') + t.true(box3.hasPoint(robox, { x: 2, y: 2, z: 2 }), 'rotated, inside') + t.true(box3.hasPoint(robox, { x: 3, y: 3, z: 3 }), 'rotated, at LB corner') + t.false(box3.hasPoint(robox, { x: 3, y: 4, z: 3 }), 'rotated, outside LB') + + t.end() + }) +} diff --git a/test/box3/index.test.js b/test/box3/index.test.js index b6b1db1d..e057b3c0 100644 --- a/test/box3/index.test.js +++ b/test/box3/index.test.js @@ -13,6 +13,7 @@ const units = { getMinimumBounds: require('./getMinimumBounds.test'), getSize: require('./getSize.test'), getVolume: require('./getVolume.test'), + hasPoint: require('./hasPoint.test'), homothety: require('./homothety.test'), projectToPlane: require('./projectToPlane.test'), resizeBy: require('./resizeBy.test'), From 63bcc78b9cc4d3ff8d4b0c5a798ecd24ac210997 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Tue, 7 Mar 2023 01:05:42 +0200 Subject: [PATCH 05/36] improve point2 docs --- lib/point2/translate.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/point2/translate.js b/lib/point2/translate.js index 4e709f42..c52bde81 100644 --- a/lib/point2/translate.js +++ b/lib/point2/translate.js @@ -3,6 +3,7 @@ module.exports = (p, vec) => { // @affineplane.point2.move // // Translate the point by the given vector. + // See affineplane.point2.offset to translate by scalars. // // Parameters: // p From a39dffb52c2dbb3a073299651f824426ffc39296 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Tue, 7 Mar 2023 01:32:09 +0200 Subject: [PATCH 06/36] test point2.offset --- test/point2/index.test.js | 1 + test/point2/offset.test.js | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 test/point2/offset.test.js diff --git a/test/point2/index.test.js b/test/point2/index.test.js index e82e027f..d778cfdc 100644 --- a/test/point2/index.test.js +++ b/test/point2/index.test.js @@ -5,6 +5,7 @@ const units = { difference: require('./difference.test'), direction: require('./direction.test'), homothety: require('./homothety.test'), + offset: require('./offset.test'), projectToLine: require('./projectToLine.test'), projectToPlane: require('./projectToPlane.test'), transformMany: require('./transformMany.test'), diff --git a/test/point2/offset.test.js b/test/point2/offset.test.js new file mode 100644 index 00000000..02aef289 --- /dev/null +++ b/test/point2/offset.test.js @@ -0,0 +1,19 @@ +const point2 = require('../../lib/point2') + +module.exports = (ts) => { + ts.test('case: basic offset', (t) => { + t.deepEqual( + point2.offset({ x: 0, y: 0 }, 1, 1), + { x: 1, y: 1 }, + 'zero point should move by unit' + ) + + t.deepEqual( + point2.offset({ x: 3, y: -3 }, 1, 1), + { x: 4, y: -2 }, + 'non zero point should move by unit' + ) + + t.end() + }) +} From c6cb430bf8dea199f7d25f75a83440e585beb3a2 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Tue, 7 Mar 2023 01:41:37 +0200 Subject: [PATCH 07/36] implement sphere2 geometry --- lib/index.js | 3 +++ lib/sphere2/almostEqual.js | 30 ++++++++++++++++++++++++++ lib/sphere2/area.js | 13 +++++++++++ lib/sphere2/collide.js | 24 +++++++++++++++++++++ lib/sphere2/copy.js | 18 ++++++++++++++++ lib/sphere2/create.js | 18 ++++++++++++++++ lib/sphere2/gap.js | 20 +++++++++++++++++ lib/sphere2/hasPoint.js | 25 +++++++++++++++++++++ lib/sphere2/homothety.js | 25 +++++++++++++++++++++ lib/sphere2/index.js | 19 ++++++++++++++++ lib/sphere2/offset.js | 22 +++++++++++++++++++ lib/sphere2/transitFrom.js | 24 +++++++++++++++++++++ lib/sphere2/transitTo.js | 26 ++++++++++++++++++++++ lib/sphere2/translate.js | 21 ++++++++++++++++++ lib/sphere2/validate.js | 29 +++++++++++++++++++++++++ test/index.test.js | 1 + test/sphere2/almostEqual.test.js | 14 ++++++++++++ test/sphere2/collide.test.js | 17 +++++++++++++++ test/sphere2/gap.test.js | 25 +++++++++++++++++++++ test/sphere2/hasPoint.test.js | 22 +++++++++++++++++++ test/sphere2/homothety.test.js | 37 ++++++++++++++++++++++++++++++++ test/sphere2/index.test.js | 18 ++++++++++++++++ test/sphere2/offset.test.js | 19 ++++++++++++++++ test/sphere2/transitFrom.test.js | 23 ++++++++++++++++++++ test/sphere2/transitTo.test.js | 12 +++++++++++ test/sphere2/validate.test.js | 20 +++++++++++++++++ 26 files changed, 525 insertions(+) create mode 100644 lib/sphere2/almostEqual.js create mode 100644 lib/sphere2/area.js create mode 100644 lib/sphere2/collide.js create mode 100644 lib/sphere2/copy.js create mode 100644 lib/sphere2/create.js create mode 100644 lib/sphere2/gap.js create mode 100644 lib/sphere2/hasPoint.js create mode 100644 lib/sphere2/homothety.js create mode 100644 lib/sphere2/index.js create mode 100644 lib/sphere2/offset.js create mode 100644 lib/sphere2/transitFrom.js create mode 100644 lib/sphere2/transitTo.js create mode 100644 lib/sphere2/translate.js create mode 100644 lib/sphere2/validate.js create mode 100644 test/sphere2/almostEqual.test.js create mode 100644 test/sphere2/collide.test.js create mode 100644 test/sphere2/gap.test.js create mode 100644 test/sphere2/hasPoint.test.js create mode 100644 test/sphere2/homothety.test.js create mode 100644 test/sphere2/index.test.js create mode 100644 test/sphere2/offset.test.js create mode 100644 test/sphere2/transitFrom.test.js create mode 100644 test/sphere2/transitTo.test.js create mode 100644 test/sphere2/validate.test.js diff --git a/lib/index.js b/lib/index.js index 772dffa5..7a18a4e5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -70,6 +70,9 @@ exports.scalar3 = require('./scalar3') exports.size2 = require('./size2') exports.size3 = require('./size3') +exports.sphere2 = require('./sphere2') +// exports.sphere3 = require('./sphere3') + exports.vec2 = require('./vec2') exports.vector2 = exports.vec2 diff --git a/lib/sphere2/almostEqual.js b/lib/sphere2/almostEqual.js new file mode 100644 index 00000000..dffe1cb1 --- /dev/null +++ b/lib/sphere2/almostEqual.js @@ -0,0 +1,30 @@ +const EPSILON = require('../epsilon') + +module.exports = (c, d, tolerance) => { + // @affineplane.sphere2.almostEqual(c, d[, tolerance]) + // + // Test if two spheres are almost equal by the margin of tolerance. + // + // Parameters + // c + // a sphere2 + // d + // a sphere2 + // tolerance + // optional number, default to affineplane.epsilon. + // .. Set to 0 for strict comparison. + // + // Return + // a boolean + // + if (typeof tolerance !== 'number') { + tolerance = EPSILON + } + + const dx = Math.abs(c.x - d.x) + const dy = Math.abs(c.y - d.y) + const dr = Math.abs(c.r - d.r) + + // Use "<=" instead of "<" to make tolerance=0 work. + return dx + dy + dr <= tolerance +} diff --git a/lib/sphere2/area.js b/lib/sphere2/area.js new file mode 100644 index 00000000..e49d08b2 --- /dev/null +++ b/lib/sphere2/area.js @@ -0,0 +1,13 @@ +module.exports = (sp) => { + // @affineplane.sphere2.area(sp) + // + // Get area of the sphere. + // + // Parameters: + // a sphere2 + // + // Return + // a scalar2, a number + // + return sp.r * sp.r * Math.PI +} diff --git a/lib/sphere2/collide.js b/lib/sphere2/collide.js new file mode 100644 index 00000000..8fd48489 --- /dev/null +++ b/lib/sphere2/collide.js @@ -0,0 +1,24 @@ +module.exports = (c, cc) => { + // @affineplane.sphere2.collide(c, cc) + // + // Detect collision between two spheres. + // + // Parameters: + // c + // a sphere2 + // cc + // a sphere2 + // + // Return + // boolean, true if the spheres collide + // + + // point distance smaller than radius sum + // sqrt(dx^2 + dy^2) <= limit + // Use <= instead of < because the same origin spheres collide even if r = 0. + const dx = cc.x - c.x + const dy = cc.y - c.y + const limit = cc.r + c.r + // Replace square root by multiplication. + return dx * dx + dy * dy <= limit * limit +} diff --git a/lib/sphere2/copy.js b/lib/sphere2/copy.js new file mode 100644 index 00000000..280eb2a2 --- /dev/null +++ b/lib/sphere2/copy.js @@ -0,0 +1,18 @@ +module.exports = (p) => { + // @affineplane.sphere2.copy(p) + // + // Copy a sphere object. + // + // Parameters + // p + // a sphere2 + // + // Return + // a sphere2 + // + return { + x: p.x, + y: p.y, + r: p.r + } +} diff --git a/lib/sphere2/create.js b/lib/sphere2/create.js new file mode 100644 index 00000000..88f912ed --- /dev/null +++ b/lib/sphere2/create.js @@ -0,0 +1,18 @@ +module.exports = (x, y, r) => { + // @affineplane.sphere2.create(x, y, r) + // + // Create a sphere2 object. + // + // Parameters: + // x + // a number + // y + // a number + // r + // a number, the radius + // + // Return + // a sphere2 + // + return { x, y, r } +} diff --git a/lib/sphere2/gap.js b/lib/sphere2/gap.js new file mode 100644 index 00000000..6c13d4e7 --- /dev/null +++ b/lib/sphere2/gap.js @@ -0,0 +1,20 @@ +module.exports = (c, cc) => { + // @affineplane.sphere2.gap(c, cc) + // + // The gap distance between the two spheres is the closest distance between + // their surfaces. The gap is zero or negative when the spheres collide. + // + // Parameters + // c + // a sphere2 + // cc + // a sphere2 + // + // Return + // a scalar1, a number, the closest distance between the sphere surfaces. + // + const dx = c.x - cc.x + const dy = c.y - cc.y + const pad = c.r + cc.r + return Math.sqrt(dx * dx + dy * dy) - pad +} diff --git a/lib/sphere2/hasPoint.js b/lib/sphere2/hasPoint.js new file mode 100644 index 00000000..a561bccd --- /dev/null +++ b/lib/sphere2/hasPoint.js @@ -0,0 +1,25 @@ +module.exports = (c, point) => { + // @affineplane.sphere2.hasPoint(c, point) + // + // Detect collision between a sphere and a point. + // + // Parameters: + // c + // a sphere2 + // point + // a point2 + // + // Return + // boolean, true if the point is inside the sphere or at the surface. + // + + const dx = point.x - c.x + const dy = point.y - c.y + const limit = c.r + // point distance smaller than radius + // sqrt(dx^2 + dy^2) <= limit + // Use <= cuz collide if point at sphere center even when r=0. + + // Replace square root by multiplication. + return dx * dx + dy * dy <= limit * limit +} diff --git a/lib/sphere2/homothety.js b/lib/sphere2/homothety.js new file mode 100644 index 00000000..b72adc76 --- /dev/null +++ b/lib/sphere2/homothety.js @@ -0,0 +1,25 @@ +module.exports = (sphere, origin, ratio) => { + // @affineplane.sphere2.homothety(sphere, origin, ratio) + // @affineplane.sphere2.scaleBy + // + // Perform homothety for the sphere about a pivot. + // In other words, scale the sphere by the given ratio, + // so that the pivot point stays fixed. + // + // Parameters: + // sphere + // a sphere2 + // origin + // a point2, the transform origin, the pivot point + // ratio + // a number, the scaling ratio + // + // Return: + // a sphere2 + // + return { + x: (sphere.x - origin.x) * ratio + origin.x, + y: (sphere.y - origin.y) * ratio + origin.y, + r: sphere.r * ratio + } +} diff --git a/lib/sphere2/index.js b/lib/sphere2/index.js new file mode 100644 index 00000000..b7ef1ef2 --- /dev/null +++ b/lib/sphere2/index.js @@ -0,0 +1,19 @@ +// @affineplane.sphere2 +// +// Two dimensional sphere, a circle. +// +// Represented with an object `{ x, y, r }` for the origin and the radius. +// +exports.almostEqual = require('./almostEqual') +exports.area = require('./area') +exports.collide = require('./collide') +exports.copy = require('./copy') +exports.create = require('./create') +exports.gap = require('./gap') +exports.hasPoint = require('./hasPoint') +exports.homothety = require('./homothety') +exports.offset = require('./offset') +exports.scaleBy = exports.homothety +exports.transitFrom = require('./transitFrom') +exports.transitTo = require('./transitTo') +exports.validate = require('./validate') diff --git a/lib/sphere2/offset.js b/lib/sphere2/offset.js new file mode 100644 index 00000000..35ac7016 --- /dev/null +++ b/lib/sphere2/offset.js @@ -0,0 +1,22 @@ +module.exports = (c, dx, dy) => { + // @affineplane.sphere2.offset(c, dx, dy) + // + // Offset a sphere by scalars dx dy. + // + // Parameters: + // c + // a sphere2 + // dx + // a number, an offset along x-axis. + // dy + // a number, an offset along y-axis. + // + // Return + // a sphere2, translated + // + return { + x: c.x + dx, + y: c.y + dy, + r: c.r + } +} diff --git a/lib/sphere2/transitFrom.js b/lib/sphere2/transitFrom.js new file mode 100644 index 00000000..ef323b8f --- /dev/null +++ b/lib/sphere2/transitFrom.js @@ -0,0 +1,24 @@ +const getScale = require('../plane2/getScale') + +module.exports = (sphere, source) => { + // @affineplane.sphere2.transitFrom(sphere, source) + // + // Transit a sphere2 from the source plane + // to the reference plane. + // + // Parameters: + // sphere + // a sphere2 on the source plane. + // source + // a plane2, the source plane, represented + // .. on the reference plane. + // + // Return: + // a sphere2, represented on the reference plane. + // + return { + x: source.a * sphere.x - source.b * sphere.y + source.x, + y: source.b * sphere.x + source.a * sphere.y + source.y, + r: sphere.r * getScale(source) + } +} diff --git a/lib/sphere2/transitTo.js b/lib/sphere2/transitTo.js new file mode 100644 index 00000000..1caf6598 --- /dev/null +++ b/lib/sphere2/transitTo.js @@ -0,0 +1,26 @@ +const invert = require('../plane2/invert') +const transitFrom = require('./transitFrom') + +module.exports = (point, target) => { + // @affineplane.sphere2.transitTo(point, target) + // + // Transit a sphere2 to a target plane. + // In other words, represent the sphere + // in the coordinate system of the target plane. + // + // Parameters: + // point + // a sphere2 on the reference plane. + // target + // a plane2, the target plane, represented + // .. on the reference plane. + // + // Return: + // a sphere2, represented on the target plane. + // + + // The plane is a mapping from itself to a target plane. + // We need the projection from the target to the plane. + const itarget = invert(target) + return transitFrom(point, itarget) +} diff --git a/lib/sphere2/translate.js b/lib/sphere2/translate.js new file mode 100644 index 00000000..a5200f32 --- /dev/null +++ b/lib/sphere2/translate.js @@ -0,0 +1,21 @@ +module.exports = (c, vec) => { + // @affineplane.sphere2.translate(c, vec) + // + // Translate the circle by the vector. + // See affineplane.sphere2.offset to translate by scalars. + // + // Parameters: + // c + // a sphere2 + // vec + // a vec2 + // + // Return + // a sphere2 + // + return { + x: c.x + vec.x, + y: c.y + vec.y, + r: c.r + } +} diff --git a/lib/sphere2/validate.js b/lib/sphere2/validate.js new file mode 100644 index 00000000..9078e16f --- /dev/null +++ b/lib/sphere2/validate.js @@ -0,0 +1,29 @@ +module.exports = (p) => { + // @affineplane.sphere2.validate(p) + // + // Check if the object is a valid sphere2. + // A valid sphere2 has x, y, r properties that are valid numbers. + // + // Parameter + // p + // an object + // + // Return + // a boolean, true if valid sphere2 + // + if (!p) { + return false + } + + if (typeof p.x !== 'number' || Number.isNaN(p.x)) { + return false + } + if (typeof p.y !== 'number' || Number.isNaN(p.y)) { + return false + } + if (typeof p.r !== 'number' || Number.isNaN(p.r)) { + return false + } + + return true +} diff --git a/test/index.test.js b/test/index.test.js index ab2fa6e5..a66ed089 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -29,6 +29,7 @@ const units = { 'affineplane.scalar1': require('./scalar1/index.test'), 'affineplane.scalar2': require('./scalar2/index.test'), 'affineplane.scalar3': require('./scalar3/index.test'), + 'affineplane.sphere2': require('./sphere2/index.test'), 'affineplane.size2': require('./size2/index.test'), 'affineplane.size3': require('./size3/index.test'), 'affineplane.vec2': require('./vec2/index.test'), diff --git a/test/sphere2/almostEqual.test.js b/test/sphere2/almostEqual.test.js new file mode 100644 index 00000000..fffa5606 --- /dev/null +++ b/test/sphere2/almostEqual.test.js @@ -0,0 +1,14 @@ +const sphere2 = require('../../lib/sphere2') + +module.exports = (ts) => { + ts.test('case: margin', (t) => { + const p = { x: 0, y: 0, r: 0 } + const q = { x: 3, y: 3, r: 3 } + t.false(sphere2.almostEqual(p, q, 1)) + t.false(sphere2.almostEqual(p, q, 8)) + t.true(sphere2.almostEqual(p, q, 9)) + t.true(sphere2.almostEqual(p, q, 10)) + + t.end() + }) +} diff --git a/test/sphere2/collide.test.js b/test/sphere2/collide.test.js new file mode 100644 index 00000000..25c1af35 --- /dev/null +++ b/test/sphere2/collide.test.js @@ -0,0 +1,17 @@ +const sphere2 = require('../../lib/sphere2') + +module.exports = (ts) => { + ts.test('case: basic collide', (t) => { + t.true( + sphere2.collide({ x: 0, y: 0, r: 0 }, { x: 0, y: 0, r: 0 }), + 'zero spheres collide' + ) + + t.false( + sphere2.collide({ x: 1, y: 0, r: 0 }, { x: 0, y: 0, r: 0 }), + 'offset zero spheres do not collide' + ) + + t.end() + }) +} diff --git a/test/sphere2/gap.test.js b/test/sphere2/gap.test.js new file mode 100644 index 00000000..528c8c15 --- /dev/null +++ b/test/sphere2/gap.test.js @@ -0,0 +1,25 @@ +const sphere2 = require('../../lib/sphere2') + +module.exports = (ts) => { + ts.test('case: basic gap', (t) => { + t.equal( + sphere2.gap({ x: 0, y: 0, r: 0 }, { x: 0, y: 0, r: 0 }), + 0, + 'zero spheres' + ) + + t.equal( + sphere2.gap({ x: 1, y: 0, r: 0 }, { x: 0, y: 0, r: 0 }), + 1, + 'offset zero spheres' + ) + + t.equal( + sphere2.gap({ x: 2, y: 2, r: 1 }, { x: 5, y: 2, r: 1 }), + 1, + 'offset zero spheres' + ) + + t.end() + }) +} diff --git a/test/sphere2/hasPoint.test.js b/test/sphere2/hasPoint.test.js new file mode 100644 index 00000000..d7379e6d --- /dev/null +++ b/test/sphere2/hasPoint.test.js @@ -0,0 +1,22 @@ +const sphere2 = require('../../lib/sphere2') + +module.exports = (ts) => { + ts.test('case: basic point collision', (t) => { + t.true( + sphere2.hasPoint({ x: 0, y: 0, r: 0 }, { x: 0, y: 0 }), + 'same origin collide' + ) + + t.false( + sphere2.hasPoint({ x: 1, y: 0, r: 0 }, { x: 0, y: 0 }), + 'offset zero sphere does not collide' + ) + + t.true( + sphere2.hasPoint({ x: 1, y: 1, r: 3 }, { x: 2, y: 2 }), + 'point inside' + ) + + t.end() + }) +} diff --git a/test/sphere2/homothety.test.js b/test/sphere2/homothety.test.js new file mode 100644 index 00000000..ba39bcfd --- /dev/null +++ b/test/sphere2/homothety.test.js @@ -0,0 +1,37 @@ +const sphere2 = require('../../lib/sphere2') + +module.exports = (ts) => { + ts.test('case: basic homothety', (t) => { + t.deepEqual( + sphere2.homothety({ x: 0, y: 0, r: 0 }, { x: 0, y: 0 }, 1), + { x: 0, y: 0, r: 0 }, + 'origin at sphere center' + ) + + t.deepEqual( + sphere2.homothety({ x: 200, y: 200, r: 100 }, { x: 0, y: 0 }, 0), + { x: 0, y: 0, r: 0 }, + 'zero factor towards zero' + ) + + t.deepEqual( + sphere2.homothety({ x: 200, y: 200, r: 100 }, { x: 1, y: 1 }, 0), + { x: 1, y: 1, r: 0 }, + 'zero factor towards non-zero' + ) + + t.deepEqual( + sphere2.homothety({ x: 2, y: -2, r: 2 }, { x: 0, y: 0 }, 0.5), + { x: 1, y: -1, r: 1 }, + 'half' + ) + + t.deepEqual( + sphere2.homothety({ x: 2, y: 2, r: 2 }, { x: 2, y: 0 }, 0.5), + { x: 2, y: 1, r: 1 }, + 'half along y axis' + ) + + t.end() + }) +} diff --git a/test/sphere2/index.test.js b/test/sphere2/index.test.js new file mode 100644 index 00000000..3838be0e --- /dev/null +++ b/test/sphere2/index.test.js @@ -0,0 +1,18 @@ +// A unit for each method. +const units = { + almostEqual: require('./almostEqual.test'), + collide: require('./collide.test'), + gap: require('./gap.test'), + hasPoint: require('./hasPoint.test'), + homothety: require('./homothety.test'), + offset: require('./offset.test'), + transitFrom: require('./transitFrom.test'), + transitTo: require('./transitTo.test'), + validate: require('./validate.test') +} + +module.exports = (t) => { + Object.keys(units).forEach((unitName) => { + t.test('affineplane.sphere2.' + unitName, units[unitName]) + }) +} diff --git a/test/sphere2/offset.test.js b/test/sphere2/offset.test.js new file mode 100644 index 00000000..8a4c90c6 --- /dev/null +++ b/test/sphere2/offset.test.js @@ -0,0 +1,19 @@ +const sphere2 = require('../../lib/sphere2') + +module.exports = (ts) => { + ts.test('case: basic offset', (t) => { + t.deepEqual( + sphere2.offset({ x: 0, y: 0, r: 1 }, 1, 1), + { x: 1, y: 1, r: 1 }, + 'should move by unit' + ) + + t.deepEqual( + sphere2.offset({ x: 3, y: -3, r: 1 }, 1, 1), + { x: 4, y: -2, r: 1 }, + 'non zero center point should move by unit' + ) + + t.end() + }) +} diff --git a/test/sphere2/transitFrom.test.js b/test/sphere2/transitFrom.test.js new file mode 100644 index 00000000..d25edb21 --- /dev/null +++ b/test/sphere2/transitFrom.test.js @@ -0,0 +1,23 @@ +const sphere2 = require('../../lib/sphere2') + +module.exports = (ts) => { + ts.test('case: basic transit from plane', (t) => { + let plane + + plane = { a: 1, b: 0, x: 2, y: 3 } + t.deepEqual( + sphere2.transitFrom({ x: 2, y: 3, r: 4 }, plane), + { x: 4, y: 6, r: 4 }, + 'translation' + ) + + plane = { a: 2, b: 0, x: 2, y: 3 } + t.deepEqual( + sphere2.transitFrom({ x: 2, y: 3, r: 4 }, plane), + { x: 6, y: 9, r: 8 }, + 'scaling and translation' + ) + + t.end() + }) +} diff --git a/test/sphere2/transitTo.test.js b/test/sphere2/transitTo.test.js new file mode 100644 index 00000000..fb50d231 --- /dev/null +++ b/test/sphere2/transitTo.test.js @@ -0,0 +1,12 @@ +const sphere2 = require('../../lib/sphere2') + +module.exports = (ts) => { + ts.test('case: basic transit to plane', (t) => { + t.deepEqual(sphere2.transitTo( + { x: 20, y: 30, r: 40 }, + { a: 1, b: 0, x: 20, y: 30 } + ), { x: 0, y: 0, r: 40 }) + + t.end() + }) +} diff --git a/test/sphere2/validate.test.js b/test/sphere2/validate.test.js new file mode 100644 index 00000000..bc64ed44 --- /dev/null +++ b/test/sphere2/validate.test.js @@ -0,0 +1,20 @@ +const sphere2 = require('../../lib/sphere2') + +module.exports = (ts) => { + ts.test('case: valid sphere2', (t) => { + t.ok(sphere2.validate({ x: 0, y: 1, r: 1 })) + t.ok(sphere2.validate({ x: 0, y: 1, r: 1, z: 2 }), 'allow additional props') + + t.end() + }) + + ts.test('case: invalid sphere2', (t) => { + t.notOk(sphere2.validate(null), 'detect null') + t.notOk(sphere2.validate({}), 'detect empty object') + t.notOk(sphere2.validate({ x: 0, y: 0 }), 'detect missing prop') + t.notOk(sphere2.validate({ x: 0, y: '1', r: 1 }), 'detect string value') + t.notOk(sphere2.validate({ x: 0, y: 0, r: NaN }), 'detect NaN') + + t.end() + }) +} From ebd32b625b7eb125edfe43e1558c7460afe8c568 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Tue, 7 Mar 2023 12:50:41 +0200 Subject: [PATCH 08/36] init box2 collide --- lib/box2/collide.js | 23 +++++++++++++++++++++++ lib/box2/index.js | 1 + 2 files changed, 24 insertions(+) create mode 100644 lib/box2/collide.js diff --git a/lib/box2/collide.js b/lib/box2/collide.js new file mode 100644 index 00000000..a6c7398e --- /dev/null +++ b/lib/box2/collide.js @@ -0,0 +1,23 @@ +module.exports = function (b, bb) { + // @affineplane.box2.collide(b, bb) + // + // Do two boxes collide? + // + // Parameters: + // b + // a box2 + // bb + // a box2 + // + // Return: + // a boolean, true if boxes collide. + // + + // Test if corner points inside. + // 4 transits and 4 tests + + // Boxes can collide without corners inside. + // Think of two long boards crossed. + + throw new Error('not implemented') +} diff --git a/lib/box2/index.js b/lib/box2/index.js index 681e8bb3..01ef082c 100644 --- a/lib/box2/index.js +++ b/lib/box2/index.js @@ -13,6 +13,7 @@ exports.almostEqual = require('./almostEqual') exports.at = require('./at') exports.atBox = require('./atBox') exports.atNorm = require('./atNorm') +exports.collide = require('./collide') exports.create = require('./create') exports.fromPoints = require('./fromPoints') exports.getAngle = require('./getAngle') From 929a5cf80f74aef6d7422b2837ee47cf3d2d62cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Akseli=20Pal=C3=A9n?= Date: Tue, 7 Mar 2023 14:40:36 +0200 Subject: [PATCH 09/36] improve sphere2 docs --- lib/sphere2/offset.js | 3 ++- lib/sphere2/transitTo.js | 8 ++++---- lib/sphere2/translate.js | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/sphere2/offset.js b/lib/sphere2/offset.js index 35ac7016..4d1861dc 100644 --- a/lib/sphere2/offset.js +++ b/lib/sphere2/offset.js @@ -1,7 +1,8 @@ module.exports = (c, dx, dy) => { // @affineplane.sphere2.offset(c, dx, dy) // - // Offset a sphere by scalars dx dy. + // Offset a sphere by scalars dx, dy. + // See affineplane.sphere2.translate to offset by a vector. // // Parameters: // c diff --git a/lib/sphere2/transitTo.js b/lib/sphere2/transitTo.js index 1caf6598..abf3dfdc 100644 --- a/lib/sphere2/transitTo.js +++ b/lib/sphere2/transitTo.js @@ -1,15 +1,15 @@ const invert = require('../plane2/invert') const transitFrom = require('./transitFrom') -module.exports = (point, target) => { - // @affineplane.sphere2.transitTo(point, target) +module.exports = (sphere, target) => { + // @affineplane.sphere2.transitTo(sphere, target) // // Transit a sphere2 to a target plane. // In other words, represent the sphere // in the coordinate system of the target plane. // // Parameters: - // point + // sphere // a sphere2 on the reference plane. // target // a plane2, the target plane, represented @@ -22,5 +22,5 @@ module.exports = (point, target) => { // The plane is a mapping from itself to a target plane. // We need the projection from the target to the plane. const itarget = invert(target) - return transitFrom(point, itarget) + return transitFrom(sphere, itarget) } diff --git a/lib/sphere2/translate.js b/lib/sphere2/translate.js index a5200f32..779ff898 100644 --- a/lib/sphere2/translate.js +++ b/lib/sphere2/translate.js @@ -1,7 +1,7 @@ module.exports = (c, vec) => { // @affineplane.sphere2.translate(c, vec) // - // Translate the circle by the vector. + // Translate the circle by the vector. Does not affect radius. // See affineplane.sphere2.offset to translate by scalars. // // Parameters: From 49088fb6a427a8c3187b102de379812ab4a102c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Akseli=20Pal=C3=A9n?= Date: Tue, 7 Mar 2023 14:40:51 +0200 Subject: [PATCH 10/36] implement sphere3 geometry --- lib/index.js | 2 +- lib/sphere3/almostEqual.js | 31 ++++++++++++++++++++ lib/sphere3/area.js | 13 +++++++++ lib/sphere3/collide.js | 25 ++++++++++++++++ lib/sphere3/copy.js | 19 +++++++++++++ lib/sphere3/create.js | 20 +++++++++++++ lib/sphere3/gap.js | 21 ++++++++++++++ lib/sphere3/hasPoint.js | 26 +++++++++++++++++ lib/sphere3/homothety.js | 26 +++++++++++++++++ lib/sphere3/index.js | 19 +++++++++++++ lib/sphere3/offset.js | 26 +++++++++++++++++ lib/sphere3/transitFrom.js | 26 +++++++++++++++++ lib/sphere3/transitTo.js | 26 +++++++++++++++++ lib/sphere3/translate.js | 22 ++++++++++++++ lib/sphere3/validate.js | 32 +++++++++++++++++++++ test/index.test.js | 1 + test/sphere3/almostEqual.test.js | 15 ++++++++++ test/sphere3/collide.test.js | 27 ++++++++++++++++++ test/sphere3/gap.test.js | 25 ++++++++++++++++ test/sphere3/hasPoint.test.js | 22 ++++++++++++++ test/sphere3/homothety.test.js | 49 ++++++++++++++++++++++++++++++++ test/sphere3/index.test.js | 18 ++++++++++++ test/sphere3/offset.test.js | 19 +++++++++++++ test/sphere3/transitFrom.test.js | 25 ++++++++++++++++ test/sphere3/transitTo.test.js | 12 ++++++++ test/sphere3/validate.test.js | 26 +++++++++++++++++ 26 files changed, 572 insertions(+), 1 deletion(-) create mode 100644 lib/sphere3/almostEqual.js create mode 100644 lib/sphere3/area.js create mode 100644 lib/sphere3/collide.js create mode 100644 lib/sphere3/copy.js create mode 100644 lib/sphere3/create.js create mode 100644 lib/sphere3/gap.js create mode 100644 lib/sphere3/hasPoint.js create mode 100644 lib/sphere3/homothety.js create mode 100644 lib/sphere3/index.js create mode 100644 lib/sphere3/offset.js create mode 100644 lib/sphere3/transitFrom.js create mode 100644 lib/sphere3/transitTo.js create mode 100644 lib/sphere3/translate.js create mode 100644 lib/sphere3/validate.js create mode 100644 test/sphere3/almostEqual.test.js create mode 100644 test/sphere3/collide.test.js create mode 100644 test/sphere3/gap.test.js create mode 100644 test/sphere3/hasPoint.test.js create mode 100644 test/sphere3/homothety.test.js create mode 100644 test/sphere3/index.test.js create mode 100644 test/sphere3/offset.test.js create mode 100644 test/sphere3/transitFrom.test.js create mode 100644 test/sphere3/transitTo.test.js create mode 100644 test/sphere3/validate.test.js diff --git a/lib/index.js b/lib/index.js index 7a18a4e5..d3629811 100644 --- a/lib/index.js +++ b/lib/index.js @@ -71,7 +71,7 @@ exports.size2 = require('./size2') exports.size3 = require('./size3') exports.sphere2 = require('./sphere2') -// exports.sphere3 = require('./sphere3') +exports.sphere3 = require('./sphere3') exports.vec2 = require('./vec2') exports.vector2 = exports.vec2 diff --git a/lib/sphere3/almostEqual.js b/lib/sphere3/almostEqual.js new file mode 100644 index 00000000..98f1b3ad --- /dev/null +++ b/lib/sphere3/almostEqual.js @@ -0,0 +1,31 @@ +const EPSILON = require('../epsilon') + +module.exports = (c, d, tolerance) => { + // @affineplane.sphere3.almostEqual(c, d[, tolerance]) + // + // Test if two spheres are almost equal by the margin of tolerance. + // + // Parameters + // c + // a sphere3 + // d + // a sphere3 + // tolerance + // optional number, default to affineplane.epsilon. + // .. Set to 0 for strict comparison. + // + // Return + // a boolean + // + if (typeof tolerance !== 'number') { + tolerance = EPSILON + } + + const dx = Math.abs(c.x - d.x) + const dy = Math.abs(c.y - d.y) + const dz = Math.abs(c.z - d.z) + const dr = Math.abs(c.r - d.r) + + // Use "<=" instead of "<" to make tolerance=0 work. + return dx + dy + dz + dr <= tolerance +} diff --git a/lib/sphere3/area.js b/lib/sphere3/area.js new file mode 100644 index 00000000..f76c0852 --- /dev/null +++ b/lib/sphere3/area.js @@ -0,0 +1,13 @@ +module.exports = (sp) => { + // @affineplane.sphere3.area(sp) + // + // Get surface area of the sphere. + // + // Parameters: + // a sphere3 + // + // Return + // a scalar2, a number. The area. + // + return sp.r * sp.r * Math.PI * 4 +} diff --git a/lib/sphere3/collide.js b/lib/sphere3/collide.js new file mode 100644 index 00000000..84a57df5 --- /dev/null +++ b/lib/sphere3/collide.js @@ -0,0 +1,25 @@ +module.exports = (c, cc) => { + // @affineplane.sphere3.collide(c, cc) + // + // Detect collision between two spheres. + // + // Parameters: + // c + // a sphere3 + // cc + // a sphere3 + // + // Return + // boolean, true if the spheres collide + // + + // point distance smaller than radius sum + // sqrt(dx^2 + dy^2 + dz^2) <= limit + // Use <= instead of < because the same origin spheres collide even if r = 0. + const dx = cc.x - c.x + const dy = cc.y - c.y + const dz = cc.z - c.z + const limit = cc.r + c.r + // Replace square root by multiplication. + return dx * dx + dy * dy + dz * dz <= limit * limit +} diff --git a/lib/sphere3/copy.js b/lib/sphere3/copy.js new file mode 100644 index 00000000..4e55b65d --- /dev/null +++ b/lib/sphere3/copy.js @@ -0,0 +1,19 @@ +module.exports = (p) => { + // @affineplane.sphere3.copy(p) + // + // Copy a sphere object. + // + // Parameters + // p + // a sphere3 + // + // Return + // a sphere3 + // + return { + x: p.x, + y: p.y, + z: p.z, + r: p.r + } +} diff --git a/lib/sphere3/create.js b/lib/sphere3/create.js new file mode 100644 index 00000000..8446e86d --- /dev/null +++ b/lib/sphere3/create.js @@ -0,0 +1,20 @@ +module.exports = (x, y, z, r) => { + // @affineplane.sphere3.create(x, y, z, r) + // + // Create a sphere3 object. + // + // Parameters: + // x + // a number + // y + // a number + // z + // a number + // r + // a number, the radius + // + // Return + // a sphere3 + // + return { x, y, z, r } +} diff --git a/lib/sphere3/gap.js b/lib/sphere3/gap.js new file mode 100644 index 00000000..36aa3598 --- /dev/null +++ b/lib/sphere3/gap.js @@ -0,0 +1,21 @@ +module.exports = (c, cc) => { + // @affineplane.sphere3.gap(c, cc) + // + // The gap distance between the two spheres is the closest distance between + // their surfaces. The gap is zero or negative when the spheres collide. + // + // Parameters + // c + // a sphere3 + // cc + // a sphere3 + // + // Return + // a scalar1, a number, the closest distance between the sphere surfaces. + // + const dx = c.x - cc.x + const dy = c.y - cc.y + const dz = c.z - cc.z + const pad = c.r + cc.r + return Math.sqrt(dx * dx + dy * dy + dz * dz) - pad +} diff --git a/lib/sphere3/hasPoint.js b/lib/sphere3/hasPoint.js new file mode 100644 index 00000000..2292d5d2 --- /dev/null +++ b/lib/sphere3/hasPoint.js @@ -0,0 +1,26 @@ +module.exports = (c, point) => { + // @affineplane.sphere3.hasPoint(c, point) + // + // Detect collision between a sphere and a point. + // + // Parameters: + // c + // a sphere3 + // point + // a point2 + // + // Return + // boolean, true if the point is inside the sphere or at the surface. + // + + const dx = point.x - c.x + const dy = point.y - c.y + const dz = point.z - c.z + const limit = c.r + // point distance smaller than radius + // sqrt(dx^2 + dy^2 + dz^2) <= limit + // Use <= cuz collide if point at sphere center even when r=0. + + // Replace square root by multiplication. + return dx * dx + dy * dy + dz * dz <= limit * limit +} diff --git a/lib/sphere3/homothety.js b/lib/sphere3/homothety.js new file mode 100644 index 00000000..00ba71d2 --- /dev/null +++ b/lib/sphere3/homothety.js @@ -0,0 +1,26 @@ +module.exports = (sphere, origin, ratio) => { + // @affineplane.sphere3.homothety(sphere, origin, ratio) + // @affineplane.sphere3.scaleBy + // + // Perform homothety for the sphere about a pivot. + // In other words, scale the sphere by the given ratio, + // so that the pivot point stays fixed. + // + // Parameters: + // sphere + // a sphere3 + // origin + // a point3, the transform origin, the pivot point + // ratio + // a number, the scaling ratio + // + // Return: + // a sphere3 + // + return { + x: (sphere.x - origin.x) * ratio + origin.x, + y: (sphere.y - origin.y) * ratio + origin.y, + z: (sphere.z - origin.z) * ratio + origin.z, + r: sphere.r * ratio + } +} diff --git a/lib/sphere3/index.js b/lib/sphere3/index.js new file mode 100644 index 00000000..62e91b43 --- /dev/null +++ b/lib/sphere3/index.js @@ -0,0 +1,19 @@ +// @affineplane.sphere3 +// +// Three dimensional sphere. +// +// Represented with an object `{ x, y, z, r }` for the origin and the radius. +// +exports.almostEqual = require('./almostEqual') +exports.area = require('./area') +exports.collide = require('./collide') +exports.copy = require('./copy') +exports.create = require('./create') +exports.gap = require('./gap') +exports.hasPoint = require('./hasPoint') +exports.homothety = require('./homothety') +exports.offset = require('./offset') +exports.scaleBy = exports.homothety +exports.transitFrom = require('./transitFrom') +exports.transitTo = require('./transitTo') +exports.validate = require('./validate') diff --git a/lib/sphere3/offset.js b/lib/sphere3/offset.js new file mode 100644 index 00000000..ec6e4b65 --- /dev/null +++ b/lib/sphere3/offset.js @@ -0,0 +1,26 @@ +module.exports = (c, dx, dy, dz = 0) => { + // @affineplane.sphere3.offset(c, dx, dy[, dz]) + // + // Offset a sphere by scalars dx, dy, dz. + // See affineplane.sphere3.translate to offset by a vector. + // + // Parameters: + // c + // a sphere3 + // dx + // a number, an offset along x-axis. + // dy + // a number, an offset along y-axis. + // dz + // optional number. The offset along z-axis, default is 0. + // + // Return + // a sphere3, translated + // + return { + x: c.x + dx, + y: c.y + dy, + z: c.z + dz, + r: c.r + } +} diff --git a/lib/sphere3/transitFrom.js b/lib/sphere3/transitFrom.js new file mode 100644 index 00000000..8c9900ca --- /dev/null +++ b/lib/sphere3/transitFrom.js @@ -0,0 +1,26 @@ +const getScale = require('../plane3/getScale') + +module.exports = (sphere, source) => { + // @affineplane.sphere3.transitFrom(sphere, source) + // + // Transit a sphere3 from the source plane + // to the reference plane. + // + // Parameters: + // sphere + // a sphere3 on the source plane. + // source + // a plane3, the source plane, represented + // .. on the reference plane. + // + // Return: + // a sphere3, represented on the reference plane. + // + const scale = getScale(source) + return { + x: source.a * sphere.x - source.b * sphere.y + source.x, + y: source.b * sphere.x + source.a * sphere.y + source.y, + z: scale * sphere.z + source.z, + r: scale * sphere.r + } +} diff --git a/lib/sphere3/transitTo.js b/lib/sphere3/transitTo.js new file mode 100644 index 00000000..5c405ffb --- /dev/null +++ b/lib/sphere3/transitTo.js @@ -0,0 +1,26 @@ +const invert = require('../plane3/invert') +const transitFrom = require('./transitFrom') + +module.exports = (sphere, target) => { + // @affineplane.sphere3.transitTo(sphere, target) + // + // Transit a sphere3 to a target plane. + // In other words, represent the sphere + // in the coordinate system of the target plane. + // + // Parameters: + // sphere + // a sphere3 on the reference plane. + // target + // a plane3, the target plane, represented + // .. on the reference plane. + // + // Return: + // a sphere3, represented on the target plane. + // + + // The plane is a mapping from itself to a target plane. + // We need the projection from the target to the plane. + const itarget = invert(target) + return transitFrom(sphere, itarget) +} diff --git a/lib/sphere3/translate.js b/lib/sphere3/translate.js new file mode 100644 index 00000000..09f4dee4 --- /dev/null +++ b/lib/sphere3/translate.js @@ -0,0 +1,22 @@ +module.exports = (c, vec) => { + // @affineplane.sphere3.translate(c, vec) + // + // Translate the sphere by the vector. Does not affect radius. + // See affineplane.sphere3.offset to translate by scalars. + // + // Parameters: + // c + // a sphere3 + // vec + // a vec3 + // + // Return + // a sphere3 + // + return { + x: c.x + vec.x, + y: c.y + vec.y, + z: c.z + vec.z, + r: c.r + } +} diff --git a/lib/sphere3/validate.js b/lib/sphere3/validate.js new file mode 100644 index 00000000..1d124d1b --- /dev/null +++ b/lib/sphere3/validate.js @@ -0,0 +1,32 @@ +module.exports = (p) => { + // @affineplane.sphere3.validate(p) + // + // Check if the object is a valid sphere3. + // A valid sphere3 has x, y, z, r properties that are valid numbers. + // + // Parameter + // p + // an object + // + // Return + // a boolean, true if valid sphere3 + // + if (!p) { + return false + } + + if (typeof p.x !== 'number' || Number.isNaN(p.x)) { + return false + } + if (typeof p.y !== 'number' || Number.isNaN(p.y)) { + return false + } + if (typeof p.z !== 'number' || Number.isNaN(p.z)) { + return false + } + if (typeof p.r !== 'number' || Number.isNaN(p.r)) { + return false + } + + return true +} diff --git a/test/index.test.js b/test/index.test.js index a66ed089..78d94ab0 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -30,6 +30,7 @@ const units = { 'affineplane.scalar2': require('./scalar2/index.test'), 'affineplane.scalar3': require('./scalar3/index.test'), 'affineplane.sphere2': require('./sphere2/index.test'), + 'affineplane.sphere3': require('./sphere3/index.test'), 'affineplane.size2': require('./size2/index.test'), 'affineplane.size3': require('./size3/index.test'), 'affineplane.vec2': require('./vec2/index.test'), diff --git a/test/sphere3/almostEqual.test.js b/test/sphere3/almostEqual.test.js new file mode 100644 index 00000000..838c8f03 --- /dev/null +++ b/test/sphere3/almostEqual.test.js @@ -0,0 +1,15 @@ +const fine = require('../../index') // should be exported +const sphere3 = fine.sphere3 + +module.exports = (ts) => { + ts.test('case: margin', (t) => { + const p = { x: 0, y: 0, z: 0, r: 0 } + const q = { x: 3, y: 3, z: 3, r: 3 } + t.false(sphere3.almostEqual(p, q, 1)) + t.false(sphere3.almostEqual(p, q, 11)) + t.true(sphere3.almostEqual(p, q, 12)) + t.true(sphere3.almostEqual(p, q, 13)) + + t.end() + }) +} diff --git a/test/sphere3/collide.test.js b/test/sphere3/collide.test.js new file mode 100644 index 00000000..04e2b0c9 --- /dev/null +++ b/test/sphere3/collide.test.js @@ -0,0 +1,27 @@ +const sphere3 = require('../../lib/sphere3') + +module.exports = (ts) => { + ts.test('case: basic collide', (t) => { + t.true( + sphere3.collide({ x: 0, y: 0, z: 0, r: 0 }, { x: 0, y: 0, z: 0, r: 0 }), + 'zero spheres collide' + ) + + t.false( + sphere3.collide({ x: 0, y: 0, z: 1, r: 0 }, { x: 0, y: 0, z: 0, r: 0 }), + 'offset zero spheres do not collide' + ) + + t.true( + sphere3.collide({ x: 0, y: 0, z: 1, r: 1 }, { x: 0, y: 0, z: -1, r: 1 }), + 'spheres touch' + ) + + t.false( + sphere3.collide({ x: 0, y: 0, z: 2, r: 1 }, { x: 0, y: 0, z: -1, r: 1 }), + 'spheres do not touch' + ) + + t.end() + }) +} diff --git a/test/sphere3/gap.test.js b/test/sphere3/gap.test.js new file mode 100644 index 00000000..f160178c --- /dev/null +++ b/test/sphere3/gap.test.js @@ -0,0 +1,25 @@ +const sphere3 = require('../../lib/sphere3') + +module.exports = (ts) => { + ts.test('case: basic gap', (t) => { + t.equal( + sphere3.gap({ x: 0, y: 0, z: 0, r: 0 }, { x: 0, y: 0, z: 0, r: 0 }), + 0, + 'zero spheres' + ) + + t.equal( + sphere3.gap({ x: 1, y: 0, z: 0, r: 0 }, { x: 0, y: 0, z: 0, r: 0 }), + 1, + 'offset zero spheres' + ) + + t.equal( + sphere3.gap({ x: 2, y: 2, z: 0, r: 1 }, { x: 5, y: 2, z: 0, r: 1 }), + 1, + 'offset zero spheres' + ) + + t.end() + }) +} diff --git a/test/sphere3/hasPoint.test.js b/test/sphere3/hasPoint.test.js new file mode 100644 index 00000000..716aea52 --- /dev/null +++ b/test/sphere3/hasPoint.test.js @@ -0,0 +1,22 @@ +const sphere3 = require('../../lib/sphere3') + +module.exports = (ts) => { + ts.test('case: basic point collision', (t) => { + t.true( + sphere3.hasPoint({ x: 0, y: 0, z: 0, r: 0 }, { x: 0, y: 0, z: 0 }), + 'same origin collide' + ) + + t.false( + sphere3.hasPoint({ x: 1, y: 0, z: 0, r: 0 }, { x: 0, y: 0, z: 0 }), + 'offset zero sphere does not collide' + ) + + t.true( + sphere3.hasPoint({ x: 1, y: 1, z: 1, r: 3 }, { x: 2, y: 2, z: 2 }), + 'point inside' + ) + + t.end() + }) +} diff --git a/test/sphere3/homothety.test.js b/test/sphere3/homothety.test.js new file mode 100644 index 00000000..5ca0736f --- /dev/null +++ b/test/sphere3/homothety.test.js @@ -0,0 +1,49 @@ +const sphere3 = require('../../lib/sphere3') + +module.exports = (ts) => { + ts.test('case: basic homothety', (t) => { + let sphere, point + + sphere = { x: 0, y: 0, z: 0, r: 0 } + point = { x: 0, y: 0, z: 0 } + t.deepEqual( + sphere3.homothety(sphere, point, 1), + { x: 0, y: 0, z: 0, r: 0 }, + 'origin at sphere center' + ) + + sphere = { x: 200, y: 200, z: 200, r: 100 } + point = { x: 0, y: 0, z: 0 } + t.deepEqual( + sphere3.homothety(sphere, point, 0), + { x: 0, y: 0, z: 0, r: 0 }, + 'zero factor towards zero' + ) + + sphere = { x: 200, y: 200, z: 200, r: 100 } + point = { x: 1, y: 1, z: 1 } + t.deepEqual( + sphere3.homothety(sphere, point, 0), + { x: 1, y: 1, z: 1, r: 0 }, + 'zero factor towards non-zero' + ) + + sphere = { x: 2, y: -2, z: 2, r: 2 } + point = { x: 0, y: 0, z: 0 } + t.deepEqual( + sphere3.homothety(sphere, point, 0.5), + { x: 1, y: -1, z: 1, r: 1 }, + 'half' + ) + + sphere = { x: 2, y: 2, z: 2, r: 2 } + point = { x: 2, y: 0, z: 0 } + t.deepEqual( + sphere3.homothety(sphere, point, 0.5), + { x: 2, y: 1, z: 1, r: 1 }, + 'half along y axis' + ) + + t.end() + }) +} diff --git a/test/sphere3/index.test.js b/test/sphere3/index.test.js new file mode 100644 index 00000000..839c5cff --- /dev/null +++ b/test/sphere3/index.test.js @@ -0,0 +1,18 @@ +// A unit for each method. +const units = { + almostEqual: require('./almostEqual.test'), + collide: require('./collide.test'), + gap: require('./gap.test'), + hasPoint: require('./hasPoint.test'), + homothety: require('./homothety.test'), + offset: require('./offset.test'), + transitFrom: require('./transitFrom.test'), + transitTo: require('./transitTo.test'), + validate: require('./validate.test') +} + +module.exports = (t) => { + Object.keys(units).forEach((unitName) => { + t.test('affineplane.sphere3.' + unitName, units[unitName]) + }) +} diff --git a/test/sphere3/offset.test.js b/test/sphere3/offset.test.js new file mode 100644 index 00000000..9bea789d --- /dev/null +++ b/test/sphere3/offset.test.js @@ -0,0 +1,19 @@ +const sphere3 = require('../../lib/sphere3') + +module.exports = (ts) => { + ts.test('case: basic offset', (t) => { + t.deepEqual( + sphere3.offset({ x: 0, y: 0, z: 0, r: 1 }, 1, 1, 1), + { x: 1, y: 1, z: 1, r: 1 }, + 'should move by unit' + ) + + t.deepEqual( + sphere3.offset({ x: 3, y: -3, z: 3, r: 1 }, 1, 1, 1), + { x: 4, y: -2, z: 4, r: 1 }, + 'non zero center point should move by unit' + ) + + t.end() + }) +} diff --git a/test/sphere3/transitFrom.test.js b/test/sphere3/transitFrom.test.js new file mode 100644 index 00000000..6b0a459a --- /dev/null +++ b/test/sphere3/transitFrom.test.js @@ -0,0 +1,25 @@ +const sphere3 = require('../../lib/sphere3') + +module.exports = (ts) => { + ts.test('case: basic transit from plane', (t) => { + let sphere, plane + + sphere = { x: 2, y: 3, z: 4, r: 4 } + plane = { a: 1, b: 0, x: 2, y: 3, z: 4 } + t.deepEqual( + sphere3.transitFrom(sphere, plane), + { x: 4, y: 6, z: 8, r: 4 }, + 'translation' + ) + + sphere = { x: 2, y: 3, z: 4, r: 4 } + plane = { a: 2, b: 0, x: 2, y: 3, z: 4 } + t.deepEqual( + sphere3.transitFrom(sphere, plane), + { x: 6, y: 9, z: 12, r: 8 }, + 'scaling and translation' + ) + + t.end() + }) +} diff --git a/test/sphere3/transitTo.test.js b/test/sphere3/transitTo.test.js new file mode 100644 index 00000000..16e92505 --- /dev/null +++ b/test/sphere3/transitTo.test.js @@ -0,0 +1,12 @@ +const sphere3 = require('../../lib/sphere3') + +module.exports = (ts) => { + ts.test('case: basic transit to plane', (t) => { + t.deepEqual(sphere3.transitTo( + { x: 20, y: 30, z: 40, r: 40 }, + { a: 1, b: 0, x: 20, y: 30, z: 40 } + ), { x: 0, y: 0, z: 0, r: 40 }) + + t.end() + }) +} diff --git a/test/sphere3/validate.test.js b/test/sphere3/validate.test.js new file mode 100644 index 00000000..a7dea0c0 --- /dev/null +++ b/test/sphere3/validate.test.js @@ -0,0 +1,26 @@ +const sphere3 = require('../../lib/sphere3') + +module.exports = (ts) => { + ts.test('case: valid sphere3', (t) => { + t.ok(sphere3.validate({ x: 0, y: 1, z: 0, r: 1 })) + t.ok( + sphere3.validate({ x: 0, y: 1, z: 0, r: 1, a: 2 }), + 'allow additional props' + ) + + t.end() + }) + + ts.test('case: invalid sphere3', (t) => { + t.notOk(sphere3.validate(null), 'detect null') + t.notOk(sphere3.validate({}), 'detect empty object') + t.notOk(sphere3.validate({ x: 0, y: 0, z: 0 }), 'detect missing prop') + t.notOk( + sphere3.validate({ x: 0, y: 0, z: '1', r: 1 }), + 'detect string value' + ) + t.notOk(sphere3.validate({ x: 0, y: 0, z: NaN, r: 1 }), 'detect NaN') + + t.end() + }) +} From ed66fefb45f9cf96b4929cc43144b7f13a8c6b35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Akseli=20Pal=C3=A9n?= Date: Tue, 7 Mar 2023 17:07:50 +0200 Subject: [PATCH 11/36] implement sphere3.projectToPlane --- lib/sphere3/index.js | 2 ++ lib/sphere3/projectToPlane.js | 44 +++++++++++++++++++++++ test/sphere3/index.test.js | 1 + test/sphere3/projectToPlane.test.js | 54 +++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 lib/sphere3/projectToPlane.js create mode 100644 test/sphere3/projectToPlane.test.js diff --git a/lib/sphere3/index.js b/lib/sphere3/index.js index 62e91b43..803e4841 100644 --- a/lib/sphere3/index.js +++ b/lib/sphere3/index.js @@ -13,6 +13,8 @@ exports.gap = require('./gap') exports.hasPoint = require('./hasPoint') exports.homothety = require('./homothety') exports.offset = require('./offset') +exports.projectToPlane = require('./projectToPlane') +exports.projectTo = exports.projectToPlane exports.scaleBy = exports.homothety exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') diff --git a/lib/sphere3/projectToPlane.js b/lib/sphere3/projectToPlane.js new file mode 100644 index 00000000..08fe8307 --- /dev/null +++ b/lib/sphere3/projectToPlane.js @@ -0,0 +1,44 @@ +const homothety = require('./homothety') +const transitTo = require('../sphere2/transitTo') + +module.exports = (sphere, plane, camera) => { + // @affineplane.sphere3.projectToPlane(sphere, plane[, camera]) + // @affineplane.sphere3.projectTo + // + // Project a 3D sphere onto a plane in 3D space. The result is a 2D sphere. + // If the camera is undefined, project orthogonally. + // + // Parameters: + // sphere + // a sphere3 in the reference space. + // plane + // a plane3 in the reference space. The target plane. + // camera + // optional point3 in the reference space. + // .. The camera position. + // + // Return: + // a sphere2 on the target plane. + // + + if (camera) { + // Find scaling ratio. + let ratio + // Distances to camera. + const distToSphere = camera.z - sphere.z + const distToPlane = camera.z - plane.z + // If camera is at the sphere depth, + // the ratio would become infinite. + if (distToSphere === 0) { + ratio = 0 // instead of infinite + } else { + ratio = distToPlane / distToSphere + } + // For perspective effect, scale towards camera. + sphere = homothety(sphere, camera, ratio) + } + + // Transit to the target plane. + // This also does orthogonal projection because z dimension is dropped. + return transitTo(sphere, plane) +} diff --git a/test/sphere3/index.test.js b/test/sphere3/index.test.js index 839c5cff..71704e4f 100644 --- a/test/sphere3/index.test.js +++ b/test/sphere3/index.test.js @@ -6,6 +6,7 @@ const units = { hasPoint: require('./hasPoint.test'), homothety: require('./homothety.test'), offset: require('./offset.test'), + projectToPlane: require('./projectToPlane.test'), transitFrom: require('./transitFrom.test'), transitTo: require('./transitTo.test'), validate: require('./validate.test') diff --git a/test/sphere3/projectToPlane.test.js b/test/sphere3/projectToPlane.test.js new file mode 100644 index 00000000..8398fd3c --- /dev/null +++ b/test/sphere3/projectToPlane.test.js @@ -0,0 +1,54 @@ +const sphere3 = require('../../lib/sphere3') + +module.exports = (ts) => { + ts.test('case: basic orthogonal projection to plane', (t) => { + const plane = { a: 1, b: 0, x: 0, y: 0, z: 0 } + t.deepEqual( + sphere3.projectTo({ x: 3, y: 0, z: 2, r: 2 }, plane), + { x: 3, y: 0, r: 2 }, + 'should remove z dim' + ) + + const planeb = { a: 1, b: 0, x: 1, y: 2, z: 3 } + t.deepEqual( + sphere3.projectTo({ x: 3, y: 0, z: 3, r: 3 }, planeb), + { x: 2, y: -2, r: 3 }, + 'should translate' + ) + + t.end() + }) + + ts.test('case: basic perspective projection to plane', (t) => { + let sphere, plane, camera + + sphere = { x: 3, y: 0, z: 0, r: 1 } + plane = { a: 1, b: 0, x: 0, y: 0, z: 0 } + camera = { x: 1, y: 1, z: 0 } + t.deepEqual( + sphere3.projectTo(sphere, plane, camera), + { x: 1, y: 1, r: 0 }, + 'camera at geometry depth' + ) + + sphere = { x: 2, y: 0, z: 0, r: 2 } + plane = { a: 1, b: 0, x: 0, y: 0, z: -2 } + camera = { x: 0, y: 0, z: -4 } + t.deepEqual( + sphere3.projectTo(sphere, plane, camera), + { x: 1, y: 0, r: 1 }, + 'camera away from projection plane' + ) + + sphere = { x: 6, y: 0, z: 2, r: 3 } + plane = { a: 1, b: 0, x: 0, y: 0, z: -2 } + camera = { x: 0, y: 0, z: -4 } + t.deepEqual( + sphere3.projectTo(sphere, plane, camera), + { x: 2, y: 0, r: 1 }, + 'geometry deeper than ref plane' + ) + + t.end() + }) +} From d8258c0582bf75a1826a9411c3d42d0217f0932a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Akseli=20Pal=C3=A9n?= Date: Tue, 7 Mar 2023 17:21:41 +0200 Subject: [PATCH 12/36] implement scalar* validate --- lib/scalar1/index.js | 1 + lib/scalar1/validate.js | 23 +++++++++++++++++++++++ lib/scalar2/index.js | 1 + lib/scalar2/validate.js | 13 +++++++++++++ lib/scalar3/index.js | 1 + lib/scalar3/validate.js | 13 +++++++++++++ test/scalar1/index.test.js | 3 ++- test/scalar1/validate.test.js | 14 ++++++++++++++ test/scalar2/index.test.js | 3 ++- test/scalar2/validate.test.js | 11 +++++++++++ test/scalar3/index.test.js | 3 ++- test/scalar3/validate.test.js | 11 +++++++++++ 12 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 lib/scalar1/validate.js create mode 100644 lib/scalar2/validate.js create mode 100644 lib/scalar3/validate.js create mode 100644 test/scalar1/validate.test.js create mode 100644 test/scalar2/validate.test.js create mode 100644 test/scalar3/validate.test.js diff --git a/lib/scalar1/index.js b/lib/scalar1/index.js index 15605a94..90dd5a7d 100644 --- a/lib/scalar1/index.js +++ b/lib/scalar1/index.js @@ -13,3 +13,4 @@ exports.create = require('./create') exports.equal = require('./equal') exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') +exports.validate = require('./validate') diff --git a/lib/scalar1/validate.js b/lib/scalar1/validate.js new file mode 100644 index 00000000..be8e4a0b --- /dev/null +++ b/lib/scalar1/validate.js @@ -0,0 +1,23 @@ +module.exports = (s) => { + // @affineplane.scalar1.validate(s) + // + // Check if the argument is a valid scalar1. + // Valid scalar1 is a number and not NaN. + // + // Parameter + // s + // a value + // + // Return + // a boolean, true if valid scalar1 + // + if (typeof s !== 'number') { + return false + } + + if (Number.isNaN(s)) { + return false + } + + return true +} diff --git a/lib/scalar2/index.js b/lib/scalar2/index.js index 589676a5..3b770a58 100644 --- a/lib/scalar2/index.js +++ b/lib/scalar2/index.js @@ -18,3 +18,4 @@ exports.create = require('./create') exports.equal = require('./equal') exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') +exports.validate = require('./validate') diff --git a/lib/scalar2/validate.js b/lib/scalar2/validate.js new file mode 100644 index 00000000..8f32dce4 --- /dev/null +++ b/lib/scalar2/validate.js @@ -0,0 +1,13 @@ +// @affineplane.scalar2.validate(s) +// +// Check if the argument is a valid scalar2. +// Valid scalar2 is a number and not NaN. +// +// Parameter +// s +// a value +// +// Return +// a boolean, true if valid scalar2 +// +module.exports = require('../scalar1/validate') diff --git a/lib/scalar3/index.js b/lib/scalar3/index.js index e0d35bb3..da8dda63 100644 --- a/lib/scalar3/index.js +++ b/lib/scalar3/index.js @@ -19,3 +19,4 @@ exports.create = require('./create') exports.equal = require('./equal') exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') +exports.validate = require('./validate') diff --git a/lib/scalar3/validate.js b/lib/scalar3/validate.js new file mode 100644 index 00000000..4dd0236e --- /dev/null +++ b/lib/scalar3/validate.js @@ -0,0 +1,13 @@ +// @affineplane.scalar3.validate(s) +// +// Check if the argument is a valid scalar3. +// Valid scalar3 is a number and not NaN. +// +// Parameter +// s +// a value +// +// Return +// a boolean, true if valid scalar3 +// +module.exports = require('../scalar1/validate') diff --git a/test/scalar1/index.test.js b/test/scalar1/index.test.js index f9773fd8..10b35bc6 100644 --- a/test/scalar1/index.test.js +++ b/test/scalar1/index.test.js @@ -4,7 +4,8 @@ const units = { create: require('./create.test'), equal: require('./equal.test'), transitFrom: require('./transitFrom.test'), - transitTo: require('./transitTo.test') + transitTo: require('./transitTo.test'), + validate: require('./validate.test') } module.exports = (t) => { diff --git a/test/scalar1/validate.test.js b/test/scalar1/validate.test.js new file mode 100644 index 00000000..98de291c --- /dev/null +++ b/test/scalar1/validate.test.js @@ -0,0 +1,14 @@ +const affineplane = require('../../index') // should be exported +const scalar1 = affineplane.scalar1 + +module.exports = (ts) => { + ts.test('case: basic scalar validation', (t) => { + t.true(scalar1.validate(0), 'zero is valid') + t.true(scalar1.validate(-1), 'negative one is valid') + t.false(scalar1.validate(null), 'null should not be valid') + t.false(scalar1.validate({}), 'empty object should not be valid') + t.false(scalar1.validate(NaN), 'NaN should not be valid') + + t.end() + }) +} diff --git a/test/scalar2/index.test.js b/test/scalar2/index.test.js index ce1cfcce..67cdc69b 100644 --- a/test/scalar2/index.test.js +++ b/test/scalar2/index.test.js @@ -4,7 +4,8 @@ const units = { create: require('./create.test'), equal: require('./equal.test'), transitFrom: require('./transitFrom.test'), - transitTo: require('./transitTo.test') + transitTo: require('./transitTo.test'), + validate: require('./validate.test') } module.exports = (t) => { diff --git a/test/scalar2/validate.test.js b/test/scalar2/validate.test.js new file mode 100644 index 00000000..c4fda79f --- /dev/null +++ b/test/scalar2/validate.test.js @@ -0,0 +1,11 @@ +const affineplane = require('../../index') // should be exported +const scalar2 = affineplane.scalar2 + +module.exports = (ts) => { + ts.test('case: smoke', (t) => { + t.true(scalar2.validate(0), 'zero is valid') + t.false(scalar2.validate(NaN), 'detect NaN') + + t.end() + }) +} diff --git a/test/scalar3/index.test.js b/test/scalar3/index.test.js index e915d558..d28864e9 100644 --- a/test/scalar3/index.test.js +++ b/test/scalar3/index.test.js @@ -4,7 +4,8 @@ const units = { create: require('./create.test'), equal: require('./equal.test'), transitFrom: require('./transitFrom.test'), - transitTo: require('./transitTo.test') + transitTo: require('./transitTo.test'), + validate: require('./validate.test') } module.exports = (t) => { diff --git a/test/scalar3/validate.test.js b/test/scalar3/validate.test.js new file mode 100644 index 00000000..c4fda79f --- /dev/null +++ b/test/scalar3/validate.test.js @@ -0,0 +1,11 @@ +const affineplane = require('../../index') // should be exported +const scalar2 = affineplane.scalar2 + +module.exports = (ts) => { + ts.test('case: smoke', (t) => { + t.true(scalar2.validate(0), 'zero is valid') + t.false(scalar2.validate(NaN), 'detect NaN') + + t.end() + }) +} From d6d2e546e40bbf5279898f3a57f72476d47ede14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Akseli=20Pal=C3=A9n?= Date: Tue, 7 Mar 2023 17:30:01 +0200 Subject: [PATCH 13/36] implement dist2 dist3 validate --- lib/dist2/index.js | 1 + lib/dist2/validate.js | 27 +++++++++++++++++++++++++++ lib/dist3/index.js | 1 + lib/dist3/validate.js | 13 +++++++++++++ test/dist2/index.test.js | 3 ++- test/dist2/validate.test.js | 15 +++++++++++++++ test/dist3/index.test.js | 3 ++- test/dist3/validate.test.js | 11 +++++++++++ 8 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 lib/dist2/validate.js create mode 100644 lib/dist3/validate.js create mode 100644 test/dist2/validate.test.js create mode 100644 test/dist3/validate.test.js diff --git a/lib/dist2/index.js b/lib/dist2/index.js index 8d0b805a..c54c1b00 100644 --- a/lib/dist2/index.js +++ b/lib/dist2/index.js @@ -15,3 +15,4 @@ exports.projectToPlane = require('./projectToPlane') exports.projectTo = exports.projectToPlane exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') +exports.validate = require('./validate') diff --git a/lib/dist2/validate.js b/lib/dist2/validate.js new file mode 100644 index 00000000..72a35889 --- /dev/null +++ b/lib/dist2/validate.js @@ -0,0 +1,27 @@ +module.exports = (d) => { + // @affineplane.dist2.validate(d) + // + // Check if the argument is a valid dist2. + // Valid dist2 is a zero or positive number and not NaN. + // + // Parameter + // d + // a value + // + // Return + // a boolean, true if valid + // + if (typeof d !== 'number') { + return false + } + + if (Number.isNaN(d)) { + return false + } + + if (d < 0) { + return false + } + + return true +} diff --git a/lib/dist3/index.js b/lib/dist3/index.js index ce29d85c..031c5468 100644 --- a/lib/dist3/index.js +++ b/lib/dist3/index.js @@ -15,3 +15,4 @@ exports.projectToPlane = require('./projectToPlane') exports.projectTo = exports.projectToPlane exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') +exports.validate = require('./validate') diff --git a/lib/dist3/validate.js b/lib/dist3/validate.js new file mode 100644 index 00000000..84919e5f --- /dev/null +++ b/lib/dist3/validate.js @@ -0,0 +1,13 @@ +// @affineplane.dist3.validate(d) +// +// Check if the argument is a valid dist3. +// Valid dist3 is a zero or positive number and not NaN. +// +// Parameter +// d +// a value +// +// Return +// a boolean, true if valid +// +module.exports = require('../dist2/validate') diff --git a/test/dist2/index.test.js b/test/dist2/index.test.js index 043f842f..9384dba6 100644 --- a/test/dist2/index.test.js +++ b/test/dist2/index.test.js @@ -5,7 +5,8 @@ const units = { equal: require('./equal.test'), projectToPlane: require('./projectToPlane.test'), transitFrom: require('./transitFrom.test'), - transitTo: require('./transitTo.test') + transitTo: require('./transitTo.test'), + validate: require('./validate.test') } module.exports = (t) => { diff --git a/test/dist2/validate.test.js b/test/dist2/validate.test.js new file mode 100644 index 00000000..b8b70122 --- /dev/null +++ b/test/dist2/validate.test.js @@ -0,0 +1,15 @@ +const affineplane = require('../../index') // should be exported +const dist2 = affineplane.dist2 + +module.exports = (ts) => { + ts.test('case: basic distance validation', (t) => { + t.true(dist2.validate(0), 'zero is valid') + t.true(dist2.validate(1), 'one is valid') + t.false(dist2.validate(-1), 'negative is not valid') + t.false(dist2.validate(null), 'null should not be valid') + t.false(dist2.validate({}), 'empty object should not be valid') + t.false(dist2.validate(NaN), 'NaN should not be valid') + + t.end() + }) +} diff --git a/test/dist3/index.test.js b/test/dist3/index.test.js index 392d2157..ea0bbdb7 100644 --- a/test/dist3/index.test.js +++ b/test/dist3/index.test.js @@ -4,7 +4,8 @@ const units = { create: require('./create.test'), projectToPlane: require('./projectToPlane.test'), transitFrom: require('./transitFrom.test'), - transitTo: require('./transitTo.test') + transitTo: require('./transitTo.test'), + validate: require('./validate.test') } module.exports = (t) => { diff --git a/test/dist3/validate.test.js b/test/dist3/validate.test.js new file mode 100644 index 00000000..56df5655 --- /dev/null +++ b/test/dist3/validate.test.js @@ -0,0 +1,11 @@ +const affineplane = require('../../index') // should be exported +const dist3 = affineplane.dist3 + +module.exports = (ts) => { + ts.test('case: smoke', (t) => { + t.true(dist3.validate(1), 'one is valid') + t.false(dist3.validate(-1), 'negative is not valid') + + t.end() + }) +} From 173770986e543eb22d12351dc5cbf6bb4df88065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Akseli=20Pal=C3=A9n?= Date: Tue, 7 Mar 2023 17:39:23 +0200 Subject: [PATCH 14/36] improve box2 docs --- lib/box2/atNorm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/box2/atNorm.js b/lib/box2/atNorm.js index 9778ee7e..a6f34e3c 100644 --- a/lib/box2/atNorm.js +++ b/lib/box2/atNorm.js @@ -3,7 +3,7 @@ const at = require('./at') module.exports = (box, nw, nh) => { // @affineplane.box2.atNorm(box, nw, nh) // - // Take a point on the box. + // Take a point on the box at the normalized coordinates. // // Parameters // box From 78b1491f8a7f19ab45b4beb931cf78e3b27289d5 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Tue, 7 Mar 2023 19:56:27 +0200 Subject: [PATCH 15/36] implement box2 getCircle --- lib/box2/getCircle.js | 27 +++++++++++++++++++++++++++ lib/box2/index.js | 2 ++ test/box2/getCircle.test.js | 31 +++++++++++++++++++++++++++++++ test/box2/index.test.js | 1 + 4 files changed, 61 insertions(+) create mode 100644 lib/box2/getCircle.js create mode 100644 test/box2/getCircle.test.js diff --git a/lib/box2/getCircle.js b/lib/box2/getCircle.js new file mode 100644 index 00000000..0315e62f --- /dev/null +++ b/lib/box2/getCircle.js @@ -0,0 +1,27 @@ +const at = require('./at') + +module.exports = (box) => { + // @affineplane.box2.getCircle(box) + // @affineplane.box2.getSphere + // + // Get the circumscribed circle of the box. In other words, get the circle + // that contains the box so that the box corners are on it. + // The resulting circle is also the minimum bounding circle of the box. + // + // Parameters + // box + // a box2, in the reference basis. + // + // Return + // a sphere2 + // + + // Circle center + const hw = box.w / 2 + const hh = box.h / 2 + const p = at(box, hw, hh) + // Patch radius + p.r = Math.sqrt(hw * hw + hh * hh) + + return p +} diff --git a/lib/box2/index.js b/lib/box2/index.js index 01ef082c..ad81401f 100644 --- a/lib/box2/index.js +++ b/lib/box2/index.js @@ -21,6 +21,8 @@ exports.getArea = require('./getArea') exports.getBasis = require('./getBasis') exports.getBasisInverse = require('./getBasisInverse') exports.getBounds = require('./getBounds') +exports.getCircle = require('./getCircle') +exports.getSphere = exports.getCircle exports.getMinimumBounds = require('./getMinimumBounds') exports.getPath = require('./getPath') exports.getPoints = exports.getPath diff --git a/test/box2/getCircle.test.js b/test/box2/getCircle.test.js new file mode 100644 index 00000000..4d285138 --- /dev/null +++ b/test/box2/getCircle.test.js @@ -0,0 +1,31 @@ +const affineplane = require('../../index') +const box2 = affineplane.box2 + +module.exports = (ts) => { + ts.test('case: basic bounding circle', (t) => { + let box + + box = { a: 1, b: 0, x: 0, y: 0, w: 8, h: 6 } + t.deepEqual( + box2.getCircle(box), + { x: 4, y: 3, r: 5 }, + 'should be correct circle' + ) + + box = { a: 1, b: 0, x: 200, y: 200, w: 8, h: 6 } + t.deepEqual( + box2.getSphere(box), + { x: 204, y: 203, r: 5 }, + 'should allow alias' + ) + + box = { a: 1, b: 0, x: 1, y: 1, w: 0, h: 0 } + t.deepEqual( + box2.getCircle(box), + { x: 1, y: 1, r: 0 }, + 'should handle empty box' + ) + + t.end() + }) +} diff --git a/test/box2/index.test.js b/test/box2/index.test.js index 202218b3..25bbe41a 100644 --- a/test/box2/index.test.js +++ b/test/box2/index.test.js @@ -11,6 +11,7 @@ const units = { getBasis: require('./getBasis.test'), getBasisInverse: require('./getBasisInverse.test'), getBounds: require('./getBounds.test'), + getCircle: require('./getCircle.test'), getMinimumBounds: require('./getMinimumBounds.test'), getPath: require('./getPath.test'), getSize: require('./getSize.test'), From 7ef867daf29f581be96e66d8848f97f316182b1e Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Tue, 7 Mar 2023 19:56:46 +0200 Subject: [PATCH 16/36] implement box3 getSphere --- lib/box3/getSphere.js | 27 +++++++++++++++++++++++++++ lib/box3/index.js | 1 + test/box3/getSphere.test.js | 32 ++++++++++++++++++++++++++++++++ test/box3/index.test.js | 1 + 4 files changed, 61 insertions(+) create mode 100644 lib/box3/getSphere.js create mode 100644 test/box3/getSphere.test.js diff --git a/lib/box3/getSphere.js b/lib/box3/getSphere.js new file mode 100644 index 00000000..9e9915da --- /dev/null +++ b/lib/box3/getSphere.js @@ -0,0 +1,27 @@ +const at = require('./at') + +module.exports = (box) => { + // @affineplane.box3.getSphere(box) + // + // Get the circumscribed sphere of the box. In other words, get the sphere + // that contains the box so that the box corners are on the sphere. + // The resulting sphere is also the minimum bounding sphere of the box. + // + // Parameters + // box + // a box3, in the reference basis. + // + // Return + // a sphere3 + // + + // Sphere center + const hw = box.w / 2 + const hh = box.h / 2 + const hd = box.d / 2 + const p = at(box, hw, hh, hd) + // Patch radius + p.r = Math.sqrt(hw * hw + hh * hh + hd * hd) + + return p +} diff --git a/lib/box3/index.js b/lib/box3/index.js index dee88fe5..2828a931 100644 --- a/lib/box3/index.js +++ b/lib/box3/index.js @@ -21,6 +21,7 @@ exports.getBasisInverse = require('./getBasisInverse') exports.getBounds = require('./getBounds') exports.getMinimumBounds = require('./getMinimumBounds') exports.getSize = require('./getSize') +exports.getSphere = require('./getSphere') exports.getVolume = require('./getVolume') exports.hasPoint = require('./hasPoint') exports.homothety = require('./homothety') diff --git a/test/box3/getSphere.test.js b/test/box3/getSphere.test.js new file mode 100644 index 00000000..5c45f06a --- /dev/null +++ b/test/box3/getSphere.test.js @@ -0,0 +1,32 @@ +const affineplane = require('../../index') +const box3 = affineplane.box3 + +module.exports = (ts) => { + ts.test('case: basic bounding sphere', (t) => { + let box + + // Tip: see pythagorean quadruple + box = { a: 1, b: 0, x: 0, y: 0, z: 0, w: 2, h: 4, d: 4 } + t.deepEqual( + box3.getSphere(box), + { x: 1, y: 2, z: 2, r: 3 }, + 'should be correct sphere' + ) + + box = { a: 1, b: 0, x: 200, y: 200, z: 200, w: 2, h: 4, d: 4 } + t.deepEqual( + box3.getSphere(box), + { x: 201, y: 202, z: 202, r: 3 }, + 'should find box center' + ) + + box = { a: 1, b: 0, x: 1, y: 1, z: 1, w: 0, h: 0, d: 0 } + t.deepEqual( + box3.getSphere(box), + { x: 1, y: 1, z: 1, r: 0 }, + 'should handle empty box' + ) + + t.end() + }) +} diff --git a/test/box3/index.test.js b/test/box3/index.test.js index e057b3c0..2f23381a 100644 --- a/test/box3/index.test.js +++ b/test/box3/index.test.js @@ -12,6 +12,7 @@ const units = { getBounds: require('./getBounds.test'), getMinimumBounds: require('./getMinimumBounds.test'), getSize: require('./getSize.test'), + getSphere: require('./getSphere.test'), getVolume: require('./getVolume.test'), hasPoint: require('./hasPoint.test'), homothety: require('./homothety.test'), From 90e01eb8991f5322c45062bc77c9ba431bd0abe2 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 9 Mar 2023 14:15:27 +0200 Subject: [PATCH 17/36] allow point in sphere*.gap --- lib/sphere2/gap.js | 4 ++-- lib/sphere3/gap.js | 4 ++-- test/sphere2/gap.test.js | 6 ++++++ test/sphere3/gap.test.js | 6 ++++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/sphere2/gap.js b/lib/sphere2/gap.js index 6c13d4e7..4ae2f643 100644 --- a/lib/sphere2/gap.js +++ b/lib/sphere2/gap.js @@ -8,13 +8,13 @@ module.exports = (c, cc) => { // c // a sphere2 // cc - // a sphere2 + // a sphere2 or a point2 // // Return // a scalar1, a number, the closest distance between the sphere surfaces. // const dx = c.x - cc.x const dy = c.y - cc.y - const pad = c.r + cc.r + const pad = c.r + (cc.r || 0) return Math.sqrt(dx * dx + dy * dy) - pad } diff --git a/lib/sphere3/gap.js b/lib/sphere3/gap.js index 36aa3598..2f54c734 100644 --- a/lib/sphere3/gap.js +++ b/lib/sphere3/gap.js @@ -8,7 +8,7 @@ module.exports = (c, cc) => { // c // a sphere3 // cc - // a sphere3 + // a sphere3 or a point3 // // Return // a scalar1, a number, the closest distance between the sphere surfaces. @@ -16,6 +16,6 @@ module.exports = (c, cc) => { const dx = c.x - cc.x const dy = c.y - cc.y const dz = c.z - cc.z - const pad = c.r + cc.r + const pad = c.r + (cc.r || 0) return Math.sqrt(dx * dx + dy * dy + dz * dz) - pad } diff --git a/test/sphere2/gap.test.js b/test/sphere2/gap.test.js index 528c8c15..28314f6c 100644 --- a/test/sphere2/gap.test.js +++ b/test/sphere2/gap.test.js @@ -20,6 +20,12 @@ module.exports = (ts) => { 'offset zero spheres' ) + t.equal( + sphere2.gap({ x: 0, y: 0, r: 1 }, { x: 3, y: 4 }), + 4, + 'should gap to point' + ) + t.end() }) } diff --git a/test/sphere3/gap.test.js b/test/sphere3/gap.test.js index f160178c..4f1f44b3 100644 --- a/test/sphere3/gap.test.js +++ b/test/sphere3/gap.test.js @@ -20,6 +20,12 @@ module.exports = (ts) => { 'offset zero spheres' ) + t.equal( + sphere3.gap({ x: 0, y: 0, z: 0, r: 1 }, { x: 1, y: 2, z: 2 }), + 2, + 'should gap to point' + ) + t.end() }) } From 42d31fbb49242789d6cf7db9937ae992334dcfd5 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 9 Mar 2023 15:42:51 +0200 Subject: [PATCH 18/36] implement line2 intersection --- lib/line2/index.js | 1 + lib/line2/intersection.js | 53 +++++++++++++++++++++++++++++++++ test/line2/index.test.js | 1 + test/line2/intersection.test.js | 51 +++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+) create mode 100644 lib/line2/intersection.js create mode 100644 test/line2/intersection.test.js diff --git a/lib/line2/index.js b/lib/line2/index.js index b12ecfd0..61cf678b 100644 --- a/lib/line2/index.js +++ b/lib/line2/index.js @@ -8,4 +8,5 @@ exports.at = require('./at') exports.create = require('./create') exports.fromPoints = require('./fromPoints') +exports.intersection = require('./intersection') exports.validate = require('./validate') diff --git a/lib/line2/intersection.js b/lib/line2/intersection.js new file mode 100644 index 00000000..b30b35c8 --- /dev/null +++ b/lib/line2/intersection.js @@ -0,0 +1,53 @@ +const pointDiff = require('../point2/difference') +const cross = require('../vec2/cross') +const epsilon = require('../epsilon') + +module.exports = (l, ll) => { + // @affineplane.line2.intersection(l, ll) + // + // Find intersection point between two lines, if any. + // If the lines are parallel but not equal, no intersection exist and + // therefore result is null. + // If the lines are equal, while the whole line + // would be the true intersection, + // the result is the origin point of the first line. + // + // Parameters + // l + // a line2 + // ll + // a line2 + // + // Return + // a point2 or null + // + + // For line intersection, see Mathematical Structures by Janke 2015, p.54. + const spanmagn = cross(l.span, ll.span) // outputs magnitude for 2D vectors + const delta = pointDiff(l.origin, ll.origin) + const deltamagn = cross(delta, ll.span) // outputs magnitude for 2D vectors + + if (Math.abs(spanmagn) < epsilon) { + // Denominator zero => lines parallel. + // If the same line, a vector between lines must be parallel with them. + if (Math.abs(deltamagn) < epsilon) { + // The same line. The whole line is the intersection + // but we pick the origin point. + return l.origin + } + // Parallel, but not the same, no intersection. + return null + } + + const numerator = deltamagn * spanmagn + const denom = spanmagn * spanmagn + + // The intersection point along the first line, in the line basis + const t = numerator / denom + + // The intersection point in the reference basis. + return { + x: l.origin.x + t * l.span.x, + y: l.origin.y + t * l.span.y + } +} diff --git a/test/line2/index.test.js b/test/line2/index.test.js index 4d466de6..7f63378e 100644 --- a/test/line2/index.test.js +++ b/test/line2/index.test.js @@ -3,6 +3,7 @@ const units = { at: require('./at.test'), create: require('./create.test'), fromPoints: require('./fromPoints.test'), + intersection: require('./intersection.test'), validate: require('./validate.test') } diff --git a/test/line2/intersection.test.js b/test/line2/intersection.test.js new file mode 100644 index 00000000..d71aeee9 --- /dev/null +++ b/test/line2/intersection.test.js @@ -0,0 +1,51 @@ +const fine = require('../../') +const line2 = fine.line2 + +module.exports = (ts) => { + ts.test('case: basic line intersection', (t) => { + let l, ll + + l = { + origin: { x: 0, y: 0 }, + span: { x: 1, y: 0 } + } + + t.deepEqual( + line2.intersection(l, l), + l.origin, + 'same line should intersect at origin' + ) + + l = { + origin: { x: 1, y: 0 }, + span: { x: 0, y: 1 } + } + ll = { + origin: { x: 0, y: 2 }, + span: { x: 1, y: 0 } + } + + t.deepEqual( + line2.intersection(l, ll), + { x: 1, y: 2 }, + 'perpendicular lines' + ) + + l = { + origin: { x: 1, y: 0 }, + span: { x: 0, y: 1 } + } + ll = { + origin: { x: 2, y: 0 }, + span: { x: 0, y: 1 } + } + + t.deepEqual( + line2.intersection(l, ll), + null, + 'parallel' + ) + + t.end() + }) +} From 11c12d34e6d45fa8c480266eb6809ee405c3eb6a Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 9 Mar 2023 16:30:47 +0200 Subject: [PATCH 19/36] improve line docs --- lib/line2/intersection.js | 2 +- lib/line3/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/line2/intersection.js b/lib/line2/intersection.js index b30b35c8..d0089f23 100644 --- a/lib/line2/intersection.js +++ b/lib/line2/intersection.js @@ -7,7 +7,7 @@ module.exports = (l, ll) => { // // Find intersection point between two lines, if any. // If the lines are parallel but not equal, no intersection exist and - // therefore result is null. + // therefore the result is null. // If the lines are equal, while the whole line // would be the true intersection, // the result is the origin point of the first line. diff --git a/lib/line3/index.js b/lib/line3/index.js index e00ea7b9..96c0710e 100644 --- a/lib/line3/index.js +++ b/lib/line3/index.js @@ -3,7 +3,7 @@ // Directed line object in 3D, // Represented as an origin point and a spanning vector. // -// A line2 is an object `{ origin: {x,y,z}, span: {x,y,z} }` +// A line3 is an object `{ origin: {x,y,z}, span: {x,y,z} }` // exports.at = require('./at') From 02401ba365b2b633426df4259a694d5e9240f47c Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 9 Mar 2023 16:31:05 +0200 Subject: [PATCH 20/36] implement line3 intersection --- lib/line3/index.js | 1 + lib/line3/intersection.js | 80 +++++++++++++++++++++++++++++++++ test/line3/index.test.js | 1 + test/line3/intersection.test.js | 66 +++++++++++++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 lib/line3/intersection.js create mode 100644 test/line3/intersection.test.js diff --git a/lib/line3/index.js b/lib/line3/index.js index 96c0710e..b88d8837 100644 --- a/lib/line3/index.js +++ b/lib/line3/index.js @@ -9,4 +9,5 @@ exports.at = require('./at') exports.create = require('./create') exports.fromPoints = require('./fromPoints') +exports.intersection = require('./intersection') exports.validate = require('./validate') diff --git a/lib/line3/intersection.js b/lib/line3/intersection.js new file mode 100644 index 00000000..0dcb3530 --- /dev/null +++ b/lib/line3/intersection.js @@ -0,0 +1,80 @@ +const pointDiff = require('../point3/difference') +const cross = require('../vec3/cross') +const dot = require('../vec3/dot') +const magnitude = require('../vec3/magnitude') +const epsilon = require('../epsilon') + +module.exports = (l, ll) => { + // @affineplane.line3.intersection(l, ll) + // + // Find intersection point between two lines, if any. + // If the lines are parallel but not equal, or the lines are skew, + // no intersection exist and therefore the result is null. + // If the lines are equal, while the whole line + // would be the true intersection. However, in this case we pick + // the origin point of the first line. + // + // Parameters + // l + // a line3 + // ll + // a line3 + // + // Return + // a point3 or null + // + + // For line intersection, see Mathematical Structures by Janke 2015, p.54. + // Basically we find such points on the lines that are closest to each other + // and then test if their distance is zero. + + const spancross = cross(l.span, ll.span) + const spanmagn = magnitude(spancross) + const delta = pointDiff(l.origin, ll.origin) + const deltacross0 = cross(delta, ll.span) + + if (Math.abs(spanmagn) < epsilon) { + // Denominator zero => lines parallel. + // If the same line, a vector between lines must be parallel with them. + if (Math.abs(magnitude(deltacross0)) < epsilon) { + // The same line. The whole line is the intersection + // but we pick the origin point. + return l.origin + } + // Parallel, but not the same, no intersection. + return null + } + + const deltacross1 = cross(delta, l.span) + + // The closest points along the lines, in the line basis. + const numerator0 = dot(deltacross0, spancross) + const numerator1 = dot(deltacross1, spancross) + const denom = spanmagn * spanmagn + const t0 = numerator0 / denom + const t1 = numerator1 / denom + + // The closest points in the reference basis. + const p0 = { + x: l.origin.x + t0 * l.span.x, + y: l.origin.y + t0 * l.span.y, + z: l.origin.z + t0 * l.span.z + } + const p1 = { + x: ll.origin.x + t1 * ll.span.x, + y: ll.origin.y + t1 * ll.span.y, + z: ll.origin.z + t1 * ll.span.z + } + + // Test if the same point. + const dx = Math.abs(p1.x - p0.x) + const dy = Math.abs(p1.y - p0.y) + const dz = Math.abs(p1.z - p0.z) + if (dx + dy + dz < epsilon) { + // Lines intersect. + return p0 + } + + // Lines do not intersect + return null +} diff --git a/test/line3/index.test.js b/test/line3/index.test.js index e33400b6..22e7c292 100644 --- a/test/line3/index.test.js +++ b/test/line3/index.test.js @@ -3,6 +3,7 @@ const units = { at: require('./at.test'), create: require('./create.test'), fromPoints: require('./fromPoints.test'), + intersection: require('./intersection.test'), validate: require('./validate.test') } diff --git a/test/line3/intersection.test.js b/test/line3/intersection.test.js new file mode 100644 index 00000000..08a90150 --- /dev/null +++ b/test/line3/intersection.test.js @@ -0,0 +1,66 @@ +const fine = require('../../') +const line3 = fine.line3 + +module.exports = (ts) => { + ts.test('case: basic line intersection', (t) => { + let l, ll + + l = { + origin: { x: 0, y: 0, z: 0 }, + span: { x: 1, y: 0, z: 1 } + } + + t.deepEqual( + line3.intersection(l, l), + l.origin, + 'same line should intersect at origin' + ) + + l = { + origin: { x: 1, y: 0, z: 0 }, + span: { x: 0, y: 1, z: 0 } + } + ll = { + origin: { x: 0, y: 2, z: 0 }, + span: { x: 1, y: 0, z: 0 } + } + + t.deepEqual( + line3.intersection(l, ll), + { x: 1, y: 2, z: 0 }, + 'perpendicular lines that intersect' + ) + + l = { + origin: { x: 1, y: 0, z: 0 }, + span: { x: 0, y: 1, z: 0 } + } + ll = { + origin: { x: 0, y: 2, z: 1 }, + span: { x: 1, y: 0, z: 1 } + } + + t.deepEqual( + line3.intersection(l, ll), + null, + 'skew lines do not intersect' + ) + + l = { + origin: { x: 1, y: 0, z: 0 }, + span: { x: 0, y: 1, z: 0 } + } + ll = { + origin: { x: 2, y: 0, z: 0 }, + span: { x: 0, y: 1, z: 0 } + } + + t.deepEqual( + line3.intersection(l, ll), + null, + 'parallel' + ) + + t.end() + }) +} From 22c75909e518067fc1f0cb1213ce2fa0d731b51e Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 9 Mar 2023 19:30:52 +0200 Subject: [PATCH 21/36] init segment2 geometry --- lib/index.js | 3 +++ lib/segment2/create.js | 16 ++++++++++++++++ lib/segment2/index.js | 9 +++++++++ test/index.test.js | 1 + test/segment2/create.test.js | 14 ++++++++++++++ test/segment2/index.test.js | 10 ++++++++++ 6 files changed, 53 insertions(+) create mode 100644 lib/segment2/create.js create mode 100644 lib/segment2/index.js create mode 100644 test/segment2/create.test.js create mode 100644 test/segment2/index.test.js diff --git a/lib/index.js b/lib/index.js index d3629811..72e8085e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -67,6 +67,9 @@ exports.scalar1 = require('./scalar1') exports.scalar2 = require('./scalar2') exports.scalar3 = require('./scalar3') +exports.segment2 = require('./segment2') +// TODO exports.segment3 = require('./segment3') + exports.size2 = require('./size2') exports.size3 = require('./size3') diff --git a/lib/segment2/create.js b/lib/segment2/create.js new file mode 100644 index 00000000..d20d9b76 --- /dev/null +++ b/lib/segment2/create.js @@ -0,0 +1,16 @@ +module.exports = (p0, p1) => { + // @affineplane.segment2.create(p0, p1) + // + // Create a segment from points. + // + // Parameters: + // p0 + // a point2 + // p1 + // a point2 + // + // Return: + // a segment2, an array. + // + return [p0, p1] +} diff --git a/lib/segment2/index.js b/lib/segment2/index.js new file mode 100644 index 00000000..c78be5ad --- /dev/null +++ b/lib/segment2/index.js @@ -0,0 +1,9 @@ +// @affineplane.segment2 +// +// Two-dimensional line segment. Represented by the segment start and end +// points in an array of length two. +// +// Example: `[{ x: 0, y: 0 }, { x: 1, y: 2 }]` +// + +exports.create = require('./create') diff --git a/test/index.test.js b/test/index.test.js index 78d94ab0..d2e0f437 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -29,6 +29,7 @@ const units = { 'affineplane.scalar1': require('./scalar1/index.test'), 'affineplane.scalar2': require('./scalar2/index.test'), 'affineplane.scalar3': require('./scalar3/index.test'), + 'affineplane.segment2': require('./segment2/index.test'), 'affineplane.sphere2': require('./sphere2/index.test'), 'affineplane.sphere3': require('./sphere3/index.test'), 'affineplane.size2': require('./size2/index.test'), diff --git a/test/segment2/create.test.js b/test/segment2/create.test.js new file mode 100644 index 00000000..e3961056 --- /dev/null +++ b/test/segment2/create.test.js @@ -0,0 +1,14 @@ +const affineplane = require('../../index') +const segment2 = affineplane.segment2 + +module.exports = (ts) => { + ts.test('case: basic create', (t) => { + t.deepEqual( + segment2.create({ x: 0, y: 0 }, { x: 1, y: 1 }), + [{ x: 0, y: 0 }, { x: 1, y: 1 }], + 'should form array' + ) + + t.end() + }) +} diff --git a/test/segment2/index.test.js b/test/segment2/index.test.js new file mode 100644 index 00000000..acc4bdc0 --- /dev/null +++ b/test/segment2/index.test.js @@ -0,0 +1,10 @@ +// A unit for each method. +const units = { + create: require('./create.test') +} + +module.exports = (t) => { + Object.keys(units).forEach((unitName) => { + t.test('affineplane.segment2.' + unitName, units[unitName]) + }) +} From 15705a5af9497fe3d56476ff4fe8d1dd9b994ee6 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Thu, 9 Mar 2023 19:31:38 +0200 Subject: [PATCH 22/36] implement segment2 collide --- lib/segment2/collide.js | 75 +++++++++++++++++++++++++++++++++++ lib/segment2/index.js | 1 + test/segment2/collide.test.js | 25 ++++++++++++ test/segment2/index.test.js | 1 + 4 files changed, 102 insertions(+) create mode 100644 lib/segment2/collide.js create mode 100644 test/segment2/collide.test.js diff --git a/lib/segment2/collide.js b/lib/segment2/collide.js new file mode 100644 index 00000000..fa65fdcd --- /dev/null +++ b/lib/segment2/collide.js @@ -0,0 +1,75 @@ +const pointDiff = require('../point2/difference') +const dot = require('../vec2/dot') +const epsilon = require('../epsilon') + +module.exports = function (s0, s1) { + // @affineplane.segment2.collide(s0, s1) + // + // Test if two segments collide. + // + // Parameters: + // s0 + // a segment2 + // s1 + // a segment2 + // + // Return: + // a boolean, true if segments collide. + // + + // See Janke 2015, p. 61: Intersection of Line Segments + // Basically, we can determine whether the segments collide without + // needing to know the intersection point. We can do that by + // finding if the endpoints are always at different sides of the segment line + // for both segments. + + // End points + const p0 = s0[0] + const p1 = s0[1] + const q0 = s1[0] + const q1 = s1[1] + // Line spanning vectors + const vp = pointDiff(p0, p1) + const vq = pointDiff(q0, q1) + + // Line normals + const np = { x: -vp.y, y: vp.x } + const nq = { x: -vq.y, y: vq.x } + // Test s0 endpoints against s1 normal. Projects to normal. + const dqp0 = dot(nq, pointDiff(p0, q0)) + const dqp1 = dot(nq, pointDiff(p1, q0)) + // Must be on different sides to intersect. Discriminant. + const qDiscr = dqp0 * dqp1 + // Escape early if no cut + if (qDiscr > 0) { + return false + } + // Test s1 endpoints against s0 normal. Projects to normal. + const dpq0 = dot(np, pointDiff(q0, p0)) + const dpq1 = dot(np, pointDiff(q1, p0)) + // Must be on different sides to intersect. Discriminant. + const pDiscr = dpq0 * dpq1 + + if (qDiscr < 0 && pDiscr < 0) { + // Segments cross. + return true + } + + // Segments can still be parallel and overlapping. + if (Math.abs(qDiscr) < epsilon && Math.abs(pDiscr) < epsilon) { + // Assume parallel because the both projected discriminants are zero. + // Test if the segments overlap. + // Represent s1 points on line s0. + const magn2 = vp.x * vp.x + vp.y * vp.y + const tq0 = dot(vp, pointDiff(p0, q0)) / magn2 + const tq1 = dot(vp, pointDiff(p0, q1)) / magn2 + + // If they at between 0..1, they on the seqment s0. + if ((tq0 >= 0 && tq0 <= 1) || (tq1 >= 0 && tq1 <= 1)) { + return true + } + } + + // No collision + return false +} diff --git a/lib/segment2/index.js b/lib/segment2/index.js index c78be5ad..93341d26 100644 --- a/lib/segment2/index.js +++ b/lib/segment2/index.js @@ -6,4 +6,5 @@ // Example: `[{ x: 0, y: 0 }, { x: 1, y: 2 }]` // +exports.collide = require('./collide') exports.create = require('./create') diff --git a/test/segment2/collide.test.js b/test/segment2/collide.test.js new file mode 100644 index 00000000..cae1a7e0 --- /dev/null +++ b/test/segment2/collide.test.js @@ -0,0 +1,25 @@ +const affineplane = require('../../index') +const segment2 = affineplane.segment2 + +module.exports = (ts) => { + ts.test('case: basic collide', (t) => { + let seg0, seg1 + + seg0 = [{ x: 0, y: 0 }, { x: 2, y: 0 }] + seg1 = [{ x: 1, y: -1 }, { x: 1, y: 1 }] + t.true(segment2.collide(seg0, seg1), 'trivial collision') + + t.true(segment2.collide(seg0, seg0), 'should collide with self') + t.true(segment2.collide(seg1, seg1), 'should collide with vertical self') + + seg0 = [{ x: 0, y: 0 }, { x: 2, y: 0 }] + seg1 = [{ x: 2, y: 0 }, { x: 4, y: 0 }] + t.true(segment2.collide(seg0, seg1), 'ends meet') + + seg0 = [{ x: 0, y: 0 }, { x: 2, y: 0 }] + seg1 = [{ x: 3, y: 0 }, { x: 5, y: 0 }] + t.false(segment2.collide(seg0, seg1), 'ends do not meet') + + t.end() + }) +} diff --git a/test/segment2/index.test.js b/test/segment2/index.test.js index acc4bdc0..733d29b6 100644 --- a/test/segment2/index.test.js +++ b/test/segment2/index.test.js @@ -1,5 +1,6 @@ // A unit for each method. const units = { + collide: require('./collide.test'), create: require('./create.test') } From 2ca731624a00f83861a2e8a5ec0bc9d2aad2cc4c Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Fri, 10 Mar 2023 16:44:59 +0200 Subject: [PATCH 23/36] implement line2.normal --- lib/line2/index.js | 1 + lib/line2/normal.js | 19 +++++++++++++++++++ test/line2/index.test.js | 1 + test/line2/normal.test.js | 14 ++++++++++++++ 4 files changed, 35 insertions(+) create mode 100644 lib/line2/normal.js create mode 100644 test/line2/normal.test.js diff --git a/lib/line2/index.js b/lib/line2/index.js index 61cf678b..ef6f5060 100644 --- a/lib/line2/index.js +++ b/lib/line2/index.js @@ -9,4 +9,5 @@ exports.at = require('./at') exports.create = require('./create') exports.fromPoints = require('./fromPoints') exports.intersection = require('./intersection') +exports.normal = require('./normal') exports.validate = require('./validate') diff --git a/lib/line2/normal.js b/lib/line2/normal.js new file mode 100644 index 00000000..ed0c8f11 --- /dev/null +++ b/lib/line2/normal.js @@ -0,0 +1,19 @@ +module.exports = (line) => { + // @affineplane.line2.normal(line) + // + // Get a normal vector for the line. It is perpendicular to the line. + // Note that a line has two normal vectors, one for each side. + // The returned normal is the one at the right hand. + // + // Parameters: + // line + // a line2 + // + // Return + // a vec2, a vector perpendicular to the line. + // + return { + x: -line.span.y, + y: line.span.x + } +} diff --git a/test/line2/index.test.js b/test/line2/index.test.js index 7f63378e..9691f5af 100644 --- a/test/line2/index.test.js +++ b/test/line2/index.test.js @@ -4,6 +4,7 @@ const units = { create: require('./create.test'), fromPoints: require('./fromPoints.test'), intersection: require('./intersection.test'), + normal: require('./normal.test'), validate: require('./validate.test') } diff --git a/test/line2/normal.test.js b/test/line2/normal.test.js new file mode 100644 index 00000000..7c12bf95 --- /dev/null +++ b/test/line2/normal.test.js @@ -0,0 +1,14 @@ +const line2 = require('../../lib/line2') + +module.exports = (ts) => { + ts.test('case: basic normal', (t) => { + const line = { origin: { x: 1, y: 1 }, span: { x: 1, y: 1 } } + t.deepEqual( + line2.normal(line), + { x: -1, y: 1 }, + 'should give right normal' + ) + + t.end() + }) +} From 5e385bc5f61c8f34574be5d0a43860de395e9475 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Fri, 10 Mar 2023 18:54:38 +0200 Subject: [PATCH 24/36] rename box2.translateBy to .offset --- lib/box2/index.js | 3 ++- lib/box2/{translateBy.js => offset.js} | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) rename lib/box2/{translateBy.js => offset.js} (87%) diff --git a/lib/box2/index.js b/lib/box2/index.js index ad81401f..7360d0d4 100644 --- a/lib/box2/index.js +++ b/lib/box2/index.js @@ -30,6 +30,7 @@ exports.getPolygon = exports.getPath exports.getSize = require('./getSize') exports.hasPoint = require('./hasPoint') exports.homothety = require('./homothety') +exports.offset = require('./offset') exports.projectToPlane = require('./projectToPlane') exports.projectTo = exports.projectToPlane exports.resizeBy = require('./resizeBy') @@ -38,5 +39,5 @@ exports.rotateBy = require('./rotateBy') exports.scaleBy = exports.homothety exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') -exports.translateBy = require('./translateBy') +exports.translateBy = exports.offset // TODO alias translate in v3 exports.validate = require('./validate') diff --git a/lib/box2/translateBy.js b/lib/box2/offset.js similarity index 87% rename from lib/box2/translateBy.js rename to lib/box2/offset.js index 96835501..082a2207 100644 --- a/lib/box2/translateBy.js +++ b/lib/box2/offset.js @@ -1,5 +1,5 @@ module.exports = (box, dx, dy) => { - // @affineplane.box2.translateBy(box, dx, dy) + // @affineplane.box2.offset(box, dx, dy) // // Move the box horizontally and vertically. // From 6b9fb51666c03eabc772fdf2a5995c97a34f54dd Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Fri, 10 Mar 2023 18:57:32 +0200 Subject: [PATCH 25/36] implement box2.translate --- lib/box2/index.js | 1 + lib/box2/translate.js | 24 +++++++++++++++++++ test/box2/index.test.js | 3 ++- .../{translateBy.test.js => offset.test.js} | 4 ++-- test/box2/translate.test.js | 14 +++++++++++ 5 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 lib/box2/translate.js rename test/box2/{translateBy.test.js => offset.test.js} (73%) create mode 100644 test/box2/translate.test.js diff --git a/lib/box2/index.js b/lib/box2/index.js index 7360d0d4..968ddfeb 100644 --- a/lib/box2/index.js +++ b/lib/box2/index.js @@ -39,5 +39,6 @@ exports.rotateBy = require('./rotateBy') exports.scaleBy = exports.homothety exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') +exports.translate = require('./translate') exports.translateBy = exports.offset // TODO alias translate in v3 exports.validate = require('./validate') diff --git a/lib/box2/translate.js b/lib/box2/translate.js new file mode 100644 index 00000000..1169c7db --- /dev/null +++ b/lib/box2/translate.js @@ -0,0 +1,24 @@ +module.exports = (box, vec) => { + // @affineplane.box2.translate(box, vec) + // + // Move the box horizontally and vertically by a vector. + // See affineplane.box2.offset to move by scalars. + // + // Parameters: + // box + // a box2 + // vec + // a vec2 + // + // Return + // a box2 + // + return { + a: box.a, + b: box.b, + x: box.x + vec.x, + y: box.y + vec.y, + w: box.w, + h: box.h + } +} diff --git a/test/box2/index.test.js b/test/box2/index.test.js index 25bbe41a..0b5bd5b9 100644 --- a/test/box2/index.test.js +++ b/test/box2/index.test.js @@ -17,13 +17,14 @@ const units = { getSize: require('./getSize.test'), hasPoint: require('./hasPoint.test'), homothety: require('./homothety.test'), + offset: require('./offset.test'), projectToPlane: require('./projectToPlane.test'), resizeBy: require('./resizeBy.test'), resizeTo: require('./resizeTo.test'), rotateBy: require('./rotateBy.test'), transitFrom: require('./transitFrom.test'), transitTo: require('./transitTo.test'), - translateBy: require('./translateBy.test'), + translate: require('./translate.test'), validate: require('./validate.test') } diff --git a/test/box2/translateBy.test.js b/test/box2/offset.test.js similarity index 73% rename from test/box2/translateBy.test.js rename to test/box2/offset.test.js index 14b24731..c03a6557 100644 --- a/test/box2/translateBy.test.js +++ b/test/box2/offset.test.js @@ -1,10 +1,10 @@ const box2 = require('../../lib/box2') module.exports = (ts) => { - ts.test('case: basic translateBy', (t) => { + ts.test('case: basic box offset', (t) => { const b = { a: 1, b: 0, x: 0, y: 0, w: 0, h: 0 } t.deepEqual( - box2.translateBy(b, 10, 20), + box2.offset(b, 10, 20), { a: 1, b: 0, x: 10, y: 20, w: 0, h: 0 }, 'trivial' ) diff --git a/test/box2/translate.test.js b/test/box2/translate.test.js new file mode 100644 index 00000000..9d01a83d --- /dev/null +++ b/test/box2/translate.test.js @@ -0,0 +1,14 @@ +const box2 = require('../../lib/box2') + +module.exports = (ts) => { + ts.test('case: basic box translate', (t) => { + const b = { a: 1, b: 0, x: 0, y: 0, w: 0, h: 0 } + t.deepEqual( + box2.translate(b, { x: 10, y: 20 }), + { a: 1, b: 0, x: 10, y: 20, w: 0, h: 0 }, + 'trivial' + ) + + t.end() + }) +} From b953170b1be67b9ae150307bdddc7d21e117975a Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Fri, 10 Mar 2023 19:04:33 +0200 Subject: [PATCH 26/36] implement box2.getSegments --- lib/box2/getSegments.js | 26 ++++++++++++++++++++++++++ lib/box2/index.js | 1 + test/box2/getSegments.test.js | 21 +++++++++++++++++++++ test/box2/index.test.js | 1 + 4 files changed, 49 insertions(+) create mode 100644 lib/box2/getSegments.js create mode 100644 test/box2/getSegments.test.js diff --git a/lib/box2/getSegments.js b/lib/box2/getSegments.js new file mode 100644 index 00000000..0f822ca3 --- /dev/null +++ b/lib/box2/getSegments.js @@ -0,0 +1,26 @@ +const at = require('./at') + +module.exports = (box) => { + // @affineplane.box2.getSegments(box) + // + // Get the four line segments of the box. + // + // Parameters: + // box + // a box2, in the reference basis. + // + // Return + // array of segment2, each segment in the reference basis. + // + const c00 = at(box, 0, 0) + const cW0 = at(box, box.w, 0) + const cWH = at(box, box.w, box.h) + const c0H = at(box, 0, box.h) + + return [ + [c00, cW0], + [cW0, cWH], + [cWH, c0H], + [c0H, c00] + ] +} diff --git a/lib/box2/index.js b/lib/box2/index.js index 968ddfeb..b5dc9eb5 100644 --- a/lib/box2/index.js +++ b/lib/box2/index.js @@ -27,6 +27,7 @@ exports.getMinimumBounds = require('./getMinimumBounds') exports.getPath = require('./getPath') exports.getPoints = exports.getPath exports.getPolygon = exports.getPath +exports.getSegments = require('./getSegments') exports.getSize = require('./getSize') exports.hasPoint = require('./hasPoint') exports.homothety = require('./homothety') diff --git a/test/box2/getSegments.test.js b/test/box2/getSegments.test.js new file mode 100644 index 00000000..c28956ef --- /dev/null +++ b/test/box2/getSegments.test.js @@ -0,0 +1,21 @@ +const affineplane = require('../../index') +const box2 = affineplane.box2 + +module.exports = (ts) => { + ts.test('case: basic box getSegments', (t) => { + const b = { a: 1, b: 0, x: 1, y: 2, w: 3, h: 4 } + + t.deepEqual( + box2.getSegments(b), + [ + [{ x: 1, y: 2 }, { x: 4, y: 2 }], + [{ x: 4, y: 2 }, { x: 4, y: 6 }], + [{ x: 4, y: 6 }, { x: 1, y: 6 }], + [{ x: 1, y: 6 }, { x: 1, y: 2 }] + ], + 'should have correct corners' + ) + + t.end() + }) +} diff --git a/test/box2/index.test.js b/test/box2/index.test.js index 0b5bd5b9..c6ea10a2 100644 --- a/test/box2/index.test.js +++ b/test/box2/index.test.js @@ -14,6 +14,7 @@ const units = { getCircle: require('./getCircle.test'), getMinimumBounds: require('./getMinimumBounds.test'), getPath: require('./getPath.test'), + getSegments: require('./getSegments.test'), getSize: require('./getSize.test'), hasPoint: require('./hasPoint.test'), homothety: require('./homothety.test'), From a2a5ac05232d1b90a2c6a4a005103db1d535051e Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Fri, 10 Mar 2023 19:05:35 +0200 Subject: [PATCH 27/36] implement box2.collide --- lib/box2/collide.js | 95 ++++++++++++++++++++++++++++++++++++--- test/box2/collide.test.js | 33 ++++++++++++++ test/box2/index.test.js | 1 + 3 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 test/box2/collide.test.js diff --git a/lib/box2/collide.js b/lib/box2/collide.js index a6c7398e..f2f81f7b 100644 --- a/lib/box2/collide.js +++ b/lib/box2/collide.js @@ -1,3 +1,19 @@ +const getBasis = require('./getBasis') +const getBasisInverse = require('./getBasisInverse') +const getCircle = require('./getCircle') +const getSegments = require('./getSegments') +const planeCompose = require('../plane2/compose') +const pointTransitFrom = require('../point2/transitFrom') +const collideCircles = require('../sphere2/collide') +const collideSegments = require('../segment2/collide') + +const inside = (box, point) => { + // Test if a point in the box basis is inside the box. + const px = point.x + const py = point.y + return px >= 0 && px <= box.w && py >= 0 && py <= box.h +} + module.exports = function (b, bb) { // @affineplane.box2.collide(b, bb) // @@ -5,19 +21,86 @@ module.exports = function (b, bb) { // // Parameters: // b - // a box2 + // a box2, in the reference basis // bb - // a box2 + // a box2, in the reference basis // // Return: // a boolean, true if boxes collide. // - // Test if corner points inside. - // 4 transits and 4 tests + // We can first quickly determine if boxes cannot collide by + // comparing their bounding spheres. If the spheres do not collide, + // the boxes cannot collide. If the spheres do collide, we need to + // test further do the boxes truly collide. - // Boxes can collide without corners inside. + // Boxes can collide without edges crossing. + // Think of nested boxes. + // Also, boxes can collide without any of the corners inside another. // Think of two long boards crossed. - throw new Error('not implemented') + // Therefore we need to check if at least one of the corners is inside + // the other box, and if not, check if any of the edge segments collide. + // We need 4x4 segment collision tests. + + // For nested boxes, we need to test if a corner point is inside. + // We do not know the nesting order, thus test both directions. + // We need to test only one corner per direction. Alternatively we could + // test each corner, which would imply 4 transits and 4 tests. + + // Test bounding circle collision + const bCircle = getCircle(b) + const bbCircle = getCircle(bb) + const circleHit = collideCircles(bCircle, bbCircle) + if (!circleHit) { + // Bounding circles do not touch, thus we can exit quickly. + return false + } + // Else the circles collide and we need to inspect further. + + // Test if boxes are nested. + // Form transitions between boxes + const bToRef = getBasis(b) + const refToB = getBasisInverse(b) + const bbToRef = getBasis(bb) + const refToBb = getBasisInverse(bb) + // bToBb is a box plane b, represented on the box plane bb. + const bToBb = planeCompose(refToBb, bToRef) + // bbToB is a box plane bb, represented on the box plane b. + const bbToB = planeCompose(refToB, bbToRef) + + // Test if top left corner of box b is inside box bb. + const corner = { x: 0, y: 0 } + const cornerInBb = pointTransitFrom(corner, bToBb) + const cornerInB = pointTransitFrom(corner, bbToB) + + const isInsideBb = inside(bb, cornerInBb) + const isInsideB = inside(b, cornerInB) + + if (isInsideB || isInsideBb) { + // Boxes are nested and therefore collide. + return true + } + // Else, boxes are not nested, but still can collide. + + // Test if box segments cross. Any crossing implies collision. + const segsB = getSegments(b) + const segsBb = getSegments(bb) + const len = 4 + + let i, j, segb, segbb + // For each b segment + for (i = 0; i < len; i += 1) { + segb = segsB[i] + // For each bb segment + for (j = 0; j < len; j += 1) { + segbb = segsBb[j] + if (collideSegments(segb, segbb)) { + return true + } + } + } + // Segments did not cross. + + return false } diff --git a/test/box2/collide.test.js b/test/box2/collide.test.js new file mode 100644 index 00000000..2db818ee --- /dev/null +++ b/test/box2/collide.test.js @@ -0,0 +1,33 @@ +const box2 = require('../../lib/box2') + +module.exports = (ts) => { + ts.test('case: basic box collision', (t) => { + let b, bb + + b = { a: 1, b: 0, x: 1, y: 1, w: 2, h: 2 } + bb = box2.translate(b, { x: 3, y: 0 }) + + t.true(box2.collide(b, b), 'should collide with self') + t.false(box2.collide(b, bb), 'should not collide translated') + + bb = box2.translate(b, { x: 2, y: 0 }) + t.true(box2.collide(b, bb), 'should collide if touching') + + b = { a: 1, b: 0, x: 0, y: 0, w: 0, h: 0 } + t.true(box2.collide(b, b), 'empty box should collide with self') + + b = { a: 1, b: 0, x: 0, y: 0, w: 10, h: 2 } + bb = { a: 0, b: 1, x: 3, y: 2, w: 10, h: 2 } + t.true(box2.collide(b, bb), 'should collide via segments') + + b = { a: 1, b: 0, x: 0, y: 0, w: 10, h: 10 } + bb = { a: 0, b: 1, x: -1, y: 0, w: 10, h: 10 } + t.false(box2.collide(b, bb), 'should not collide within circles') + + b = { a: 1, b: 0, x: 0, y: 0, w: 10, h: 10 } + bb = { a: 1, b: 0, x: 2, y: 2, w: 6, h: 6 } + t.true(box2.collide(b, bb), 'nested should collide') + + t.end() + }) +} diff --git a/test/box2/index.test.js b/test/box2/index.test.js index c6ea10a2..53e9708b 100644 --- a/test/box2/index.test.js +++ b/test/box2/index.test.js @@ -4,6 +4,7 @@ const units = { at: require('./at.test'), atBox: require('./atBox.test'), atNorm: require('./atNorm.test'), + collide: require('./collide.test'), create: require('./create.test'), fromPoints: require('./fromPoints.test'), getAngle: require('./getAngle.test'), From 667e8f209ae58da3376e24a7b06ddcf9cbb694e3 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Sat, 11 Mar 2023 00:51:26 +0200 Subject: [PATCH 28/36] implement path2.validate --- lib/path2/index.js | 1 + lib/path2/validate.js | 17 +++++++++++++++++ test/path2/index.test.js | 3 ++- test/path2/validate.test.js | 19 +++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 lib/path2/validate.js create mode 100644 test/path2/validate.test.js diff --git a/lib/path2/index.js b/lib/path2/index.js index a69707a5..1fbdd5d1 100644 --- a/lib/path2/index.js +++ b/lib/path2/index.js @@ -10,3 +10,4 @@ exports.combine = require('./combine') exports.create = require('./create') exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') +exports.validate = require('./validate') diff --git a/lib/path2/validate.js b/lib/path2/validate.js new file mode 100644 index 00000000..89a31570 --- /dev/null +++ b/lib/path2/validate.js @@ -0,0 +1,17 @@ +const validatePoint = require('../point2/validate') + +module.exports = (p) => { + // @affineplane.path2.validate(p) + // + // Check if the object is a valid path2. + // A valid path2 is an array of valid point2 objects. + // + // Parameter + // p + // an object + // + // Return + // a boolean, true if valid + // + return Array.isArray(p) && p.every(point => validatePoint(point)) +} diff --git a/test/path2/index.test.js b/test/path2/index.test.js index 1e2d1bc6..cbaed1b8 100644 --- a/test/path2/index.test.js +++ b/test/path2/index.test.js @@ -3,7 +3,8 @@ const units = { combine: require('./combine.test'), create: require('./create.test'), transitFrom: require('./transitFrom.test'), - transitTo: require('./transitTo.test') + transitTo: require('./transitTo.test'), + validate: require('./validate.test') } module.exports = (t) => { diff --git a/test/path2/validate.test.js b/test/path2/validate.test.js new file mode 100644 index 00000000..c02049c2 --- /dev/null +++ b/test/path2/validate.test.js @@ -0,0 +1,19 @@ +const path2 = require('../../index').path2 + +module.exports = (ts) => { + ts.test('case: valid path2', (t) => { + t.ok(path2.validate([]), 'allow empty') + t.ok(path2.validate([{ x: 1, y: 2 }, { x: 2, y: 3 }]), 'two points') + + t.end() + }) + + ts.test('case: invalid path2', (t) => { + t.notOk(path2.validate(null), 'detect null') + t.notOk(path2.validate({}), 'detect empty object') + t.notOk(path2.validate([{ x: 0 }]), 'detect invalid point') + t.notOk(path2.validate([{ x: 0, y: 1 }, '0,1']), 'detect string') + + t.end() + }) +} From 36c53cd3c5a56994f11495b14620541f65f11a81 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Sat, 11 Mar 2023 00:55:06 +0200 Subject: [PATCH 29/36] implement path3.validate --- lib/path3/index.js | 1 + lib/path3/validate.js | 17 +++++++++++++++++ test/path3/index.test.js | 3 ++- test/path3/validate.test.js | 25 +++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 lib/path3/validate.js create mode 100644 test/path3/validate.test.js diff --git a/lib/path3/index.js b/lib/path3/index.js index a9cf3881..fe936d12 100644 --- a/lib/path3/index.js +++ b/lib/path3/index.js @@ -12,3 +12,4 @@ exports.projectToPlane = require('./projectToPlane') exports.projectTo = exports.projectToPlane exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') +exports.validate = require('./validate') diff --git a/lib/path3/validate.js b/lib/path3/validate.js new file mode 100644 index 00000000..94144eae --- /dev/null +++ b/lib/path3/validate.js @@ -0,0 +1,17 @@ +const validatePoint = require('../point3/validate') + +module.exports = (p) => { + // @affineplane.path3.validate(p) + // + // Check if the object is a valid path3. + // A valid path3 is an array of valid point3 objects. + // + // Parameter + // p + // an object + // + // Return + // a boolean, true if valid + // + return Array.isArray(p) && p.every(point => validatePoint(point)) +} diff --git a/test/path3/index.test.js b/test/path3/index.test.js index 76115403..4a525895 100644 --- a/test/path3/index.test.js +++ b/test/path3/index.test.js @@ -4,7 +4,8 @@ const units = { create: require('./create.test'), projectToPlane: require('./projectToPlane.test'), transitFrom: require('./transitFrom.test'), - transitTo: require('./transitTo.test') + transitTo: require('./transitTo.test'), + validate: require('./validate.test') } module.exports = (t) => { diff --git a/test/path3/validate.test.js b/test/path3/validate.test.js new file mode 100644 index 00000000..28003409 --- /dev/null +++ b/test/path3/validate.test.js @@ -0,0 +1,25 @@ +const path3 = require('../../index').path3 + +module.exports = (ts) => { + ts.test('case: valid path3', (t) => { + t.ok( + path3.validate([]), + 'allow empty' + ) + t.ok( + path3.validate([{ x: 1, y: 2, z: 3 }, { x: 2, y: 3, z: 4 }]), + 'two points' + ) + + t.end() + }) + + ts.test('case: invalid path3', (t) => { + t.notOk(path3.validate(null), 'detect null') + t.notOk(path3.validate({}), 'detect empty object') + t.notOk(path3.validate([{ x: 0 }]), 'detect invalid point') + t.notOk(path3.validate([{ x: 0, y: 1, z: 2 }, '0,1']), 'detect string') + + t.end() + }) +} From 1863e7c4b363602c04f82c611d8dd52a31cc4ea3 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Sat, 11 Mar 2023 01:00:33 +0200 Subject: [PATCH 30/36] implement segment2 .transitFrom .transitTo .validate --- lib/segment2/index.js | 3 +++ lib/segment2/transitFrom.js | 16 ++++++++++++++++ lib/segment2/transitTo.js | 16 ++++++++++++++++ lib/segment2/validate.js | 18 ++++++++++++++++++ test/segment2/index.test.js | 5 ++++- test/segment2/transitFrom.test.js | 19 +++++++++++++++++++ test/segment2/transitTo.test.js | 19 +++++++++++++++++++ test/segment2/validate.test.js | 25 +++++++++++++++++++++++++ 8 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 lib/segment2/transitFrom.js create mode 100644 lib/segment2/transitTo.js create mode 100644 lib/segment2/validate.js create mode 100644 test/segment2/transitFrom.test.js create mode 100644 test/segment2/transitTo.test.js create mode 100644 test/segment2/validate.test.js diff --git a/lib/segment2/index.js b/lib/segment2/index.js index 93341d26..80c69476 100644 --- a/lib/segment2/index.js +++ b/lib/segment2/index.js @@ -8,3 +8,6 @@ exports.collide = require('./collide') exports.create = require('./create') +exports.transitFrom = require('./transitFrom') +exports.transitTo = require('./transitTo') +exports.validate = require('./validate') diff --git a/lib/segment2/transitFrom.js b/lib/segment2/transitFrom.js new file mode 100644 index 00000000..851b8075 --- /dev/null +++ b/lib/segment2/transitFrom.js @@ -0,0 +1,16 @@ +// @affineplane.segment2.transitFrom(seg, source) +// +// Represent a segment on the reference basis. In other words, +// transit the segment from the source basis to the reference basis. +// +// Parameters: +// seg +// a segment2, represented on the source basis. +// source +// a plane2, the source basis, represented +// .. on the reference basis. +// +// Return: +// a segment2, represented on the reference basis. +// +module.exports = require('../path2/transitFrom') diff --git a/lib/segment2/transitTo.js b/lib/segment2/transitTo.js new file mode 100644 index 00000000..c55178d2 --- /dev/null +++ b/lib/segment2/transitTo.js @@ -0,0 +1,16 @@ +// @affineplane.segment2.transitTo(seg, target) +// +// Represent a segment in the target basis. In other words, +// transit the segment from the reference basis to the target basis. +// +// Parameters: +// seg +// a segment2, represented on the reference basis. +// target +// a plane2, the target basis, represented +// .. on the reference basis. +// +// Return: +// a segment2, represented in the target basis. +// +module.exports = require('../path2/transitTo') diff --git a/lib/segment2/validate.js b/lib/segment2/validate.js new file mode 100644 index 00000000..0de7e91b --- /dev/null +++ b/lib/segment2/validate.js @@ -0,0 +1,18 @@ +const validatePoint = require('../point2/validate') + +module.exports = (seg) => { + // @affineplane.segment2.validate(seg) + // + // Check if the object is a valid segment2. + // A valid segment2 is an array of two valid point2 objects. + // + // Parameter + // seg + // an object + // + // Return + // a boolean, true if valid + // + return Array.isArray(seg) && seg.length === 2 && + validatePoint(seg[0]) && validatePoint(seg[1]) +} diff --git a/test/segment2/index.test.js b/test/segment2/index.test.js index 733d29b6..e27cf622 100644 --- a/test/segment2/index.test.js +++ b/test/segment2/index.test.js @@ -1,7 +1,10 @@ // A unit for each method. const units = { collide: require('./collide.test'), - create: require('./create.test') + create: require('./create.test'), + transitFrom: require('./transitFrom.test'), + transitTo: require('./transitTo.test'), + validate: require('./validate.test') } module.exports = (t) => { diff --git a/test/segment2/transitFrom.test.js b/test/segment2/transitFrom.test.js new file mode 100644 index 00000000..5e9d9a4f --- /dev/null +++ b/test/segment2/transitFrom.test.js @@ -0,0 +1,19 @@ +const affineplane = require('../../index') +const segment2 = affineplane.segment2 + +module.exports = (ts) => { + ts.test('case: basic transitFrom', (t) => { + // let seg, plane + + const seg = [{ x: 0, y: 0 }, { x: 1, y: 1 }] + const basis = { a: 2, b: 0, x: 0, y: 2 } + + t.deepEqual( + segment2.transitFrom(seg, basis), + [{ x: 0, y: 2 }, { x: 2, y: 4 }], + 'scale and translation should affect' + ) + + t.end() + }) +} diff --git a/test/segment2/transitTo.test.js b/test/segment2/transitTo.test.js new file mode 100644 index 00000000..ddcaa72b --- /dev/null +++ b/test/segment2/transitTo.test.js @@ -0,0 +1,19 @@ +const affineplane = require('../../index') +const segment2 = affineplane.segment2 + +module.exports = (ts) => { + ts.test('case: basic transitTo', (t) => { + // let seg, plane + + const seg = [{ x: 0, y: 2 }, { x: 2, y: 4 }] + const plane = { a: 2, b: 0, x: 0, y: 2 } + + t.deepEqual( + segment2.transitTo(seg, plane), + [{ x: 0, y: 0 }, { x: 1, y: 1 }], + 'scale and translation should affect' + ) + + t.end() + }) +} diff --git a/test/segment2/validate.test.js b/test/segment2/validate.test.js new file mode 100644 index 00000000..b7372490 --- /dev/null +++ b/test/segment2/validate.test.js @@ -0,0 +1,25 @@ +const segment2 = require('../../index').segment2 + +module.exports = (ts) => { + ts.test('case: valid segment2', (t) => { + t.ok(segment2.validate([{ x: 1, y: 2 }, { x: 2, y: 3 }]), 'two points') + + t.end() + }) + + ts.test('case: invalid segment2', (t) => { + t.notOk(segment2.validate([]), 'detect empty') + t.notOk(segment2.validate(null), 'detect null') + t.notOk(segment2.validate({}), 'detect empty object') + t.notOk( + segment2.validate([{ x: 0, y: 1 }, { x: 0 }]), + 'detect invalid point' + ) + t.notOk( + segment2.validate([{ x: 0, y: 1 }, '0,1']), + 'detect string' + ) + + t.end() + }) +} From 4f35d61428ea7893acb22af093bee473b5b078fb Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Sat, 11 Mar 2023 01:12:09 +0200 Subject: [PATCH 31/36] implement sphere3.volume --- lib/sphere3/index.js | 1 + lib/sphere3/volume.js | 15 +++++++++++++++ test/sphere3/index.test.js | 3 ++- test/sphere3/volume.test.js | 18 ++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 lib/sphere3/volume.js create mode 100644 test/sphere3/volume.test.js diff --git a/lib/sphere3/index.js b/lib/sphere3/index.js index 803e4841..327e524c 100644 --- a/lib/sphere3/index.js +++ b/lib/sphere3/index.js @@ -19,3 +19,4 @@ exports.scaleBy = exports.homothety exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') exports.validate = require('./validate') +exports.volume = require('./volume') diff --git a/lib/sphere3/volume.js b/lib/sphere3/volume.js new file mode 100644 index 00000000..14aa2d2f --- /dev/null +++ b/lib/sphere3/volume.js @@ -0,0 +1,15 @@ +const alpha = 4 * Math.PI / 3 + +module.exports = (sp) => { + // @affineplane.sphere3.volume(sp) + // + // Get volume of the sphere. + // + // Parameters: + // a sphere3 + // + // Return + // a scalar3, a number. The volume. + // + return alpha * sp.r * sp.r * sp.r +} diff --git a/test/sphere3/index.test.js b/test/sphere3/index.test.js index 71704e4f..28a570bd 100644 --- a/test/sphere3/index.test.js +++ b/test/sphere3/index.test.js @@ -9,7 +9,8 @@ const units = { projectToPlane: require('./projectToPlane.test'), transitFrom: require('./transitFrom.test'), transitTo: require('./transitTo.test'), - validate: require('./validate.test') + validate: require('./validate.test'), + volume: require('./volume.test') } module.exports = (t) => { diff --git a/test/sphere3/volume.test.js b/test/sphere3/volume.test.js new file mode 100644 index 00000000..4aef3dc8 --- /dev/null +++ b/test/sphere3/volume.test.js @@ -0,0 +1,18 @@ +const sphere3 = require('../../lib/sphere3') + +module.exports = (ts) => { + ts.test('case: sphere volume', (t) => { + let ball + + ball = { x: 0, y: 1, z: 0, r: 0 } + t.equal(sphere3.volume(ball), 0, 'empty sphere') + + ball = { x: 0, y: 1, z: 0, r: 1 } + t.equal(sphere3.volume(ball), 4 * Math.PI / 3, 'unit sphere') + + ball = { x: 0, y: 1, z: 0, r: 2 } + t.equal(sphere3.volume(ball), 32 * Math.PI / 3, 'two sphere') + + t.end() + }) +} From 4916deb2aaae6ec4eae807aeb528920001eae8d5 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Sat, 11 Mar 2023 01:20:26 +0200 Subject: [PATCH 32/36] improve box and point homothety docs --- lib/box2/homothety.js | 16 ++++++++-------- lib/box3/homothety.js | 4 ++-- lib/point2/homothety.js | 14 +++++++------- lib/point3/homothety.js | 14 ++++++++------ 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/lib/box2/homothety.js b/lib/box2/homothety.js index 9c2001b9..0e976334 100644 --- a/lib/box2/homothety.js +++ b/lib/box2/homothety.js @@ -1,18 +1,18 @@ const pointHomothety = require('../point2/homothety') const scaleSize = require('../size2/scaleBy') -module.exports = (box, center, ratio) => { - // @affineplane.box2.homothety(box, center, ratio) - // @affineplane.box2.scaleBy(box, center, ratio) +module.exports = (box, origin, ratio) => { + // @affineplane.box2.homothety(box, origin, ratio) + // @affineplane.box2.scaleBy(box, origin, ratio) // - // Perform homothety about the center. In other words, scale the box - // about the fixed center point. + // Perform homothety about the origin. In other words, scale the box + // about the fixed pivot point. // // Parameters: // box // a box2, the box to be scaled. - // center - // a point2, the fixed pivot point. + // origin + // a point2, the fixed origin point. // ratio // a number, the scaling ratio. // @@ -21,7 +21,7 @@ module.exports = (box, center, ratio) => { // // Scale component-wise - const boxOrigin = pointHomothety(box, center, ratio) + const boxOrigin = pointHomothety(box, origin, ratio) const boxSize = scaleSize(box, ratio) // Patch size. diff --git a/lib/box3/homothety.js b/lib/box3/homothety.js index 4a5b6a41..59078ce6 100644 --- a/lib/box3/homothety.js +++ b/lib/box3/homothety.js @@ -1,7 +1,7 @@ const pointHomothety = require('../point3/homothety') const scaleSize = require('../size3/scaleBy') -module.exports = (box, center, ratio) => { +module.exports = (box, origin, ratio) => { // @affineplane.box3.homothety(box, origin, ratio) // @affineplane.box3.scaleBy(box, origin, ratio) // @@ -21,7 +21,7 @@ module.exports = (box, center, ratio) => { // // Scale component-wise - const boxOrigin = pointHomothety(box, center, ratio) + const boxOrigin = pointHomothety(box, origin, ratio) const boxSize = scaleSize(box, ratio) // Build from size. diff --git a/lib/point2/homothety.js b/lib/point2/homothety.js index 43449c5a..f91ecc12 100644 --- a/lib/point2/homothety.js +++ b/lib/point2/homothety.js @@ -1,13 +1,13 @@ -module.exports = (point, center, ratio) => { - // @affineplane.point2.homothety(point, center, ratio) +module.exports = (point, origin, ratio) => { + // @affineplane.point2.homothety(point, origin, ratio) // - // Perform homothety about the center. + // Perform homothety about the origin point. // // Parameters: // point // a point2 - // center - // a point2 + // origin + // a point2, the pivot point // ratio // a number, the scaling ratio // @@ -15,7 +15,7 @@ module.exports = (point, center, ratio) => { // a point2 // return { - x: (point.x - center.x) * ratio + center.x, - y: (point.y - center.y) * ratio + center.y + x: (point.x - origin.x) * ratio + origin.x, + y: (point.y - origin.y) * ratio + origin.y } } diff --git a/lib/point3/homothety.js b/lib/point3/homothety.js index 6153cece..67fa175c 100644 --- a/lib/point3/homothety.js +++ b/lib/point3/homothety.js @@ -1,10 +1,12 @@ -module.exports = (point, center, ratio) => { - // Perform a homothety about the center. +module.exports = (point, origin, ratio) => { + // @affineplane.point3.homothety(point, origin, ratio) + // + // Perform a homothety about the origin point. // // Parameters: // point // a point3 - // center + // origin // a point3 // ratio // a number @@ -13,8 +15,8 @@ module.exports = (point, center, ratio) => { // a point3 // return { - x: (point.x - center.x) * ratio + center.x, - y: (point.y - center.y) * ratio + center.y, - z: (point.z - center.z) * ratio + center.z + x: (point.x - origin.x) * ratio + origin.x, + y: (point.y - origin.y) * ratio + origin.y, + z: (point.z - origin.z) * ratio + origin.z } } From 8102cdb236a084dcc263591c1311b372c8fce7e7 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Sat, 11 Mar 2023 01:27:14 +0200 Subject: [PATCH 33/36] rename box3 translateBy to offset --- lib/box3/index.js | 3 ++- lib/box3/{translateBy.js => offset.js} | 0 test/box3/index.test.js | 2 +- test/box3/{translateBy.test.js => offset.test.js} | 0 4 files changed, 3 insertions(+), 2 deletions(-) rename lib/box3/{translateBy.js => offset.js} (100%) rename test/box3/{translateBy.test.js => offset.test.js} (100%) diff --git a/lib/box3/index.js b/lib/box3/index.js index 2828a931..29511dbb 100644 --- a/lib/box3/index.js +++ b/lib/box3/index.js @@ -25,6 +25,7 @@ exports.getSphere = require('./getSphere') exports.getVolume = require('./getVolume') exports.hasPoint = require('./hasPoint') exports.homothety = require('./homothety') +exports.offset = require('./offset') exports.projectToPlane = require('./projectToPlane') exports.projectTo = exports.projectToPlane exports.resizeBy = require('./resizeBy') @@ -33,5 +34,5 @@ exports.rotateBy = require('./rotateBy') exports.scaleBy = exports.homothety exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') -exports.translateBy = require('./translateBy') +exports.translateBy = exports.offset exports.validate = require('./validate') diff --git a/lib/box3/translateBy.js b/lib/box3/offset.js similarity index 100% rename from lib/box3/translateBy.js rename to lib/box3/offset.js diff --git a/test/box3/index.test.js b/test/box3/index.test.js index 2f23381a..b6d28c90 100644 --- a/test/box3/index.test.js +++ b/test/box3/index.test.js @@ -16,13 +16,13 @@ const units = { getVolume: require('./getVolume.test'), hasPoint: require('./hasPoint.test'), homothety: require('./homothety.test'), + offset: require('./offset.test'), projectToPlane: require('./projectToPlane.test'), resizeBy: require('./resizeBy.test'), resizeTo: require('./resizeTo.test'), rotateBy: require('./rotateBy.test'), transitFrom: require('./transitFrom.test'), transitTo: require('./transitTo.test'), - translateBy: require('./translateBy.test'), validate: require('./validate.test') } diff --git a/test/box3/translateBy.test.js b/test/box3/offset.test.js similarity index 100% rename from test/box3/translateBy.test.js rename to test/box3/offset.test.js From 59c7844dddea69bae99845853e0e4844c0a61d3b Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Sat, 11 Mar 2023 01:27:38 +0200 Subject: [PATCH 34/36] implement box3.translate --- lib/box3/index.js | 1 + lib/box3/translate.js | 26 ++++++++++++++++++++++++++ test/box3/index.test.js | 1 + test/box3/translate.test.js | 14 ++++++++++++++ 4 files changed, 42 insertions(+) create mode 100644 lib/box3/translate.js create mode 100644 test/box3/translate.test.js diff --git a/lib/box3/index.js b/lib/box3/index.js index 29511dbb..2f9472bc 100644 --- a/lib/box3/index.js +++ b/lib/box3/index.js @@ -34,5 +34,6 @@ exports.rotateBy = require('./rotateBy') exports.scaleBy = exports.homothety exports.transitFrom = require('./transitFrom') exports.transitTo = require('./transitTo') +exports.translate = require('./translate') exports.translateBy = exports.offset exports.validate = require('./validate') diff --git a/lib/box3/translate.js b/lib/box3/translate.js new file mode 100644 index 00000000..df39764e --- /dev/null +++ b/lib/box3/translate.js @@ -0,0 +1,26 @@ +module.exports = (box, vec) => { + // @affineplane.box3.translate(box, vec) + // + // Move the box by the given vector. + // See affineplane.box3.offset to translate by scalars. + // + // Parameters: + // box + // a box3 + // vec + // a vec3 + // + // Return + // a box3 + // + return { + a: box.a, + b: box.b, + x: box.x + vec.x, + y: box.y + vec.y, + z: box.z + vec.z, + w: box.w, + h: box.h, + d: box.d + } +} diff --git a/test/box3/index.test.js b/test/box3/index.test.js index b6d28c90..9a2fe735 100644 --- a/test/box3/index.test.js +++ b/test/box3/index.test.js @@ -23,6 +23,7 @@ const units = { rotateBy: require('./rotateBy.test'), transitFrom: require('./transitFrom.test'), transitTo: require('./transitTo.test'), + translate: require('./translate.test'), validate: require('./validate.test') } diff --git a/test/box3/translate.test.js b/test/box3/translate.test.js new file mode 100644 index 00000000..867922fd --- /dev/null +++ b/test/box3/translate.test.js @@ -0,0 +1,14 @@ +const box3 = require('../../lib/box3') + +module.exports = (ts) => { + ts.test('case: basic box translate', (t) => { + const b = { a: 1, b: 0, x: 0, y: 0, z: 0, w: 0, h: 0, d: 0 } + t.deepEqual( + box3.translate(b, { x: 10, y: 20, z: 30 }), + { a: 1, b: 0, x: 10, y: 20, z: 30, w: 0, h: 0, d: 0 }, + 'trivial' + ) + + t.end() + }) +} From 5c63c10c7c3e3031e85a2b7fe490ab588815b970 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Sat, 11 Mar 2023 01:29:38 +0200 Subject: [PATCH 35/36] render docs --- docs/API.md | 1261 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 1226 insertions(+), 35 deletions(-) diff --git a/docs/API.md b/docs/API.md index 84259c02..8621dd29 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,5 +1,5 @@ -# Affineplane API Documentation v2.11.0 +# Affineplane API Documentation v2.12.0 Welcome to affineplane API reference documentation. These docs are generated with [yamdog](https://axelpale.github.io/yamdog/). @@ -43,8 +43,11 @@ The functions are grouped in the following submodules. - [affineplane.scalar1](#affineplanescalar1) - [affineplane.scalar2](#affineplanescalar2) - [affineplane.scalar3](#affineplanescalar3) +- [affineplane.segment2](#affineplanesegment2) - [affineplane.size2](#affineplanesize2) - [affineplane.size3](#affineplanesize3) +- [affineplane.sphere2](#affineplanesphere2) +- [affineplane.sphere3](#affineplanesphere3) - [affineplane.vec2](#affineplanevec2) - [affineplane.vec3](#affineplanevec3) - [affineplane.vec4](#affineplanevec4) @@ -148,6 +151,7 @@ and thus can be represented in any basis without loss of information. - [affineplane.box2.at](#affineplanebox2at) - [affineplane.box2.atBox](#affineplanebox2atbox) - [affineplane.box2.atNorm](#affineplanebox2atnorm) +- [affineplane.box2.collide](#affineplanebox2collide) - [affineplane.box2.create](#affineplanebox2create) - [affineplane.box2.fromPoints](#affineplanebox2frompoints) - [affineplane.box2.getAngle](#affineplanebox2getangle) @@ -155,12 +159,17 @@ and thus can be represented in any basis without loss of information. - [affineplane.box2.getBasis](#affineplanebox2getbasis) - [affineplane.box2.getBasisInverse](#affineplanebox2getbasisinverse) - [affineplane.box2.getBounds](#affineplanebox2getbounds) +- [affineplane.box2.getCircle](#affineplanebox2getcircle) - [affineplane.box2.getMinimumBounds](#affineplanebox2getminimumbounds) - [affineplane.box2.getPath](#affineplanebox2getpath) - [affineplane.box2.getPoints](#affineplanebox2getpoints) - [affineplane.box2.getPolygon](#affineplanebox2getpolygon) +- [affineplane.box2.getSegments](#affineplanebox2getsegments) - [affineplane.box2.getSize](#affineplanebox2getsize) +- [affineplane.box2.getSphere](#affineplanebox2getsphere) +- [affineplane.box2.hasPoint](#affineplanebox2haspoint) - [affineplane.box2.homothety](#affineplanebox2homothety) +- [affineplane.box2.offset](#affineplanebox2offset) - [affineplane.box2.projectTo](#affineplanebox2projectto) - [affineplane.box2.projectToPlane](#affineplanebox2projecttoplane) - [affineplane.box2.resizeBy](#affineplanebox2resizeby) @@ -169,7 +178,7 @@ and thus can be represented in any basis without loss of information. - [affineplane.box2.scaleBy](#affineplanebox2scaleby) - [affineplane.box2.transitFrom](#affineplanebox2transitfrom) - [affineplane.box2.transitTo](#affineplanebox2transitto) -- [affineplane.box2.translateBy](#affineplanebox2translateby) +- [affineplane.box2.translate](#affineplanebox2translate) - [affineplane.box2.validate](#affineplanebox2validate) @@ -245,7 +254,7 @@ Source: [atBox.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/at ## [affineplane](#affineplane).[box2](#affineplanebox2).[atNorm](#affineplanebox2atnorm)(box, nw, nh) -Take a point on the box. +Take a point on the box at the normalized coordinates.

Parameters:

@@ -264,6 +273,26 @@ Take a point on the box. Source: [atNorm.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/atNorm.js) + +## [affineplane](#affineplane).[box2](#affineplanebox2).[collide](#affineplanebox2collide)(b, bb) + +Do two boxes collide? + +

Parameters:

+ +- *b* + - a [box2](#affineplanebox2), in the reference basis +- *bb* + - a [box2](#affineplanebox2), in the reference basis + + +

Returns:

+ +- a boolean, true if boxes collide. + + +Source: [collide.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/collide.js) + ## [affineplane](#affineplane).[box2](#affineplanebox2).[create](#affineplanebox2create)(plane, size) @@ -404,6 +433,28 @@ the given box or boxes. Source: [getBounds.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/getBounds.js) + +## [affineplane](#affineplane).[box2](#affineplanebox2).[getCircle](#affineplanebox2getcircle)(box) + +Get the circumscribed circle of the box. In other words, get the circle +that contains the box so that the box corners are on it. +The resulting circle is also the minimum bounding circle of the box. + +

Parameters:

+ +- *box* + - a [box2](#affineplanebox2), in the reference basis. + + +

Returns:

+ +- a sphere2 + + +Aliases: [affineplane.box2.getSphere](#affineplanebox2getsphere) + +Source: [getCircle.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/getCircle.js) + ## [affineplane](#affineplane).[box2](#affineplanebox2).[getMinimumBounds](#affineplanebox2getminimumbounds)(boxes) @@ -458,6 +509,24 @@ Alias of [affineplane.box2.getPath](#affineplanebox2getpath) Source: [getPath.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/getPath.js) + +## [affineplane](#affineplane).[box2](#affineplanebox2).[getSegments](#affineplanebox2getsegments)(box) + +Get the four line segments of the box. + +

Parameters:

+ +- *box* + - a [box2](#affineplanebox2), in the reference basis. + + +

Returns:

+ +- array of segment2, each segment in the reference basis. + + +Source: [getSegments.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/getSegments.js) + ## [affineplane](#affineplane).[box2](#affineplanebox2).[getSize](#affineplanebox2getsize)(box) @@ -476,18 +545,46 @@ Get the size of the box. Source: [getSize.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/getSize.js) + +## [affineplane](#affineplane).[box2](#affineplanebox2).[getSphere](#affineplanebox2getsphere) + +Alias of [affineplane.box2.getCircle](#affineplanebox2getcircle) + +Source: [getCircle.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/getCircle.js) + + +## [affineplane](#affineplane).[box2](#affineplanebox2).[hasPoint](#affineplanebox2haspoint)(box, point) + +Test if a point is inside the box. If the point is at the box edge, +it is counted as being inside. + +

Parameters:

+ +- *box* + - a [box2](#affineplanebox2), in the reference basis. +- *point* + - a [point2](#affineplanepoint2), in the reference basis. + + +

Returns:

+ +- a boolean + + +Source: [hasPoint.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/hasPoint.js) + -## [affineplane](#affineplane).[box2](#affineplanebox2).[homothety](#affineplanebox2homothety)(box, center, ratio) +## [affineplane](#affineplane).[box2](#affineplanebox2).[homothety](#affineplanebox2homothety)(box, origin, ratio) -Perform homothety about the center. In other words, scale the box -about the fixed center point. +Perform homothety about the origin. In other words, scale the box +about the fixed pivot point.

Parameters:

- *box* - a [box2](#affineplanebox2), the box to be scaled. -- *center* - - a [point2](#affineplanepoint2), the fixed pivot point. +- *origin* + - a [point2](#affineplanepoint2), the fixed origin point. - *ratio* - a number, the scaling ratio. @@ -501,6 +598,28 @@ Aliases: [affineplane.box2.scaleBy](#affineplanebox2scaleby) Source: [homothety.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/homothety.js) + +## [affineplane](#affineplane).[box2](#affineplanebox2).[offset](#affineplanebox2offset)(box, dx, dy) + +Move the box horizontally and vertically. + +

Parameters:

+ +- *box* + - a [box2](#affineplanebox2) +- *dx* + - a number +- *dy* + - a number + + +

Returns:

+ +- a [box2](#affineplanebox2) + + +Source: [offset.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/offset.js) + ## [affineplane](#affineplane).[box2](#affineplanebox2).[projectTo](#affineplanebox2projectto) @@ -611,7 +730,7 @@ Rotation direction is from positive x axis towards positive y axis. Source: [rotateBy.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/rotateBy.js) -## [affineplane](#affineplane).[box2](#affineplanebox2).[scaleBy](#affineplanebox2scaleby)(box, center, ratio) +## [affineplane](#affineplane).[box2](#affineplanebox2).[scaleBy](#affineplanebox2scaleby)(box, origin, ratio) Alias of [affineplane.box2.homothety](#affineplanebox2homothety) @@ -657,19 +776,18 @@ Convert a box from the reference basis to the target basis. Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/transitTo.js) - -## [affineplane](#affineplane).[box2](#affineplanebox2).[translateBy](#affineplanebox2translateby)(box, dx, dy) + +## [affineplane](#affineplane).[box2](#affineplanebox2).[translate](#affineplanebox2translate)(box, vec) -Move the box horizontally and vertically. +Move the box horizontally and vertically by a vector. +See [affineplane.box2.offset](#affineplanebox2offset) to move by scalars.

Parameters:

- *box* - a [box2](#affineplanebox2) -- *dx* - - a number -- *dy* - - a number +- *vec* + - a [vec2](#affineplanevec2)

Returns:

@@ -677,7 +795,7 @@ Move the box horizontally and vertically. - a [box2](#affineplanebox2) -Source: [translateBy.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/translateBy.js) +Source: [translate.js](https://github.com/axelpale/affineplane/blob/main/lib/box2/translate.js) ## [affineplane](#affineplane).[box2](#affineplanebox2).[validate](#affineplanebox2validate)(b) @@ -726,10 +844,13 @@ thus can be represented in any basis without loss of information. - [affineplane.box3.fromPoints](#affineplanebox3frompoints) - [affineplane.box3.getAngle](#affineplanebox3getangle) - [affineplane.box3.getBasis](#affineplanebox3getbasis) +- [affineplane.box3.getBasisInverse](#affineplanebox3getbasisinverse) - [affineplane.box3.getBounds](#affineplanebox3getbounds) - [affineplane.box3.getMinimumBounds](#affineplanebox3getminimumbounds) - [affineplane.box3.getSize](#affineplanebox3getsize) +- [affineplane.box3.getSphere](#affineplanebox3getsphere) - [affineplane.box3.getVolume](#affineplanebox3getvolume) +- [affineplane.box3.hasPoint](#affineplanebox3haspoint) - [affineplane.box3.homothety](#affineplanebox3homothety) - [affineplane.box3.projectTo](#affineplanebox3projectto) - [affineplane.box3.projectToPlane](#affineplanebox3projecttoplane) @@ -739,6 +860,7 @@ thus can be represented in any basis without loss of information. - [affineplane.box3.scaleBy](#affineplanebox3scaleby) - [affineplane.box3.transitFrom](#affineplanebox3transitfrom) - [affineplane.box3.transitTo](#affineplanebox3transitto) +- [affineplane.box3.translate](#affineplanebox3translate) - [affineplane.box3.translateBy](#affineplanebox3translateby) - [affineplane.box3.validate](#affineplanebox3validate) @@ -918,6 +1040,26 @@ The scale of the resulting basis is always 1. Source: [getBasis.js](https://github.com/axelpale/affineplane/blob/main/lib/box3/getBasis.js) + +## [affineplane](#affineplane).[box3](#affineplanebox3).[getBasisInverse](#affineplanebox3getbasisinverse)(box) + +Get the outer basis of the box represented in the box basis. +The scale of the resulting basis is always 1. +See also [affineplane.box3.getBasis](#affineplanebox3getbasis). + +

Parameters:

+ +- *box* + - a [box3](#affineplanebox3) in the reference basis. + + +

Returns:

+ +- a [plane3](#affineplaneplane3) in the box basis. The outer basis. + + +Source: [getBasisInverse.js](https://github.com/axelpale/affineplane/blob/main/lib/box3/getBasisInverse.js) + ## [affineplane](#affineplane).[box3](#affineplanebox3).[getBounds](#affineplanebox3getbounds)(boxes) @@ -980,6 +1122,26 @@ Get the size of the box. Source: [getSize.js](https://github.com/axelpale/affineplane/blob/main/lib/box3/getSize.js) + +## [affineplane](#affineplane).[box3](#affineplanebox3).[getSphere](#affineplanebox3getsphere)(box) + +Get the circumscribed sphere of the box. In other words, get the sphere +that contains the box so that the box corners are on the sphere. +The resulting sphere is also the minimum bounding sphere of the box. + +

Parameters:

+ +- *box* + - a [box3](#affineplanebox3), in the reference basis. + + +

Returns:

+ +- a sphere3 + + +Source: [getSphere.js](https://github.com/axelpale/affineplane/blob/main/lib/box3/getSphere.js) + ## [affineplane](#affineplane).[box3](#affineplanebox3).[getVolume](#affineplanebox3getvolume)(box) @@ -999,6 +1161,27 @@ in the reference basis. Source: [getVolume.js](https://github.com/axelpale/affineplane/blob/main/lib/box3/getVolume.js) + +## [affineplane](#affineplane).[box3](#affineplanebox3).[hasPoint](#affineplanebox3haspoint)(box, point) + +Test if a point is inside the box. If the point is at the box edge, +it is counted as being inside. + +

Parameters:

+ +- *box* + - a [box3](#affineplanebox3), in the reference basis. +- *point* + - a [point3](#affineplanepoint3), in the reference basis. + + +

Returns:

+ +- a boolean + + +Source: [hasPoint.js](https://github.com/axelpale/affineplane/blob/main/lib/box3/hasPoint.js) + ## [affineplane](#affineplane).[box3](#affineplanebox3).[homothety](#affineplanebox3homothety)(box, origin, ratio) @@ -1188,6 +1371,27 @@ Convert a box from the reference basis to the target basis. Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/box3/transitTo.js) + +## [affineplane](#affineplane).[box3](#affineplanebox3).[translate](#affineplanebox3translate)(box, vec) + +Move the box by the given vector. +See [affineplane.box3](#affineplanebox3).offset to translate by scalars. + +

Parameters:

+ +- *box* + - a [box3](#affineplanebox3) +- *vec* + - a [vec3](#affineplanevec3) + + +

Returns:

+ +- a [box3](#affineplanebox3) + + +Source: [translate.js](https://github.com/axelpale/affineplane/blob/main/lib/box3/translate.js) + ## [affineplane](#affineplane).[box3](#affineplanebox3).[translateBy](#affineplanebox3translateby)(box, dx, dy, dz) @@ -1210,7 +1414,7 @@ Move the box along x, y, and/or z axis by the given amounts. - a [box3](#affineplanebox3) -Source: [translateBy.js](https://github.com/axelpale/affineplane/blob/main/lib/box3/translateBy.js) +Source: [offset.js](https://github.com/axelpale/affineplane/blob/main/lib/box3/offset.js) ## [affineplane](#affineplane).[box3](#affineplanebox3).[validate](#affineplanebox3validate)(b) @@ -1716,6 +1920,7 @@ change the distance measure. - [affineplane.dist2.projectToPlane](#affineplanedist2projecttoplane) - [affineplane.dist2.transitFrom](#affineplanedist2transitfrom) - [affineplane.dist2.transitTo](#affineplanedist2transitto) +- [affineplane.dist2.validate](#affineplanedist2validate) Source: [dist2/index.js](https://github.com/axelpale/affineplane/blob/main/lib/dist2/index.js) @@ -1858,6 +2063,25 @@ in the coordinate system of the target. Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/dist2/transitTo.js) + +## [affineplane](#affineplane).[dist2](#affineplanedist2).[validate](#affineplanedist2validate)(d) + +Check if the argument is a valid [dist2](#affineplanedist2). +Valid [dist2](#affineplanedist2) is a zero or positive number and not NaN. + +

Parameters:

+ +- *d* + - a value + + +

Returns:

+ +- a boolean, true if valid + + +Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/dist2/validate.js) + ## [affineplane](#affineplane).[dist3](#affineplanedist3) @@ -1879,6 +2103,7 @@ change the distance measure. - [affineplane.dist3.projectToPlane](#affineplanedist3projecttoplane) - [affineplane.dist3.transitFrom](#affineplanedist3transitfrom) - [affineplane.dist3.transitTo](#affineplanedist3transitto) +- [affineplane.dist3.validate](#affineplanedist3validate) Source: [dist3/index.js](https://github.com/axelpale/affineplane/blob/main/lib/dist3/index.js) @@ -2023,6 +2248,25 @@ in the coordinate system of the basis. Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/dist3/transitTo.js) + +## [affineplane](#affineplane).[dist3](#affineplanedist3).[validate](#affineplanedist3validate)(d) + +Check if the argument is a valid [dist3](#affineplanedist3). +Valid [dist3](#affineplanedist3) is a zero or positive number and not NaN. + +

Parameters:

+ +- *d* + - a value + + +

Returns:

+ +- a boolean, true if valid + + +Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/dist3/validate.js) + ## [affineplane](#affineplane).[epsilon](#affineplaneepsilon) @@ -3735,6 +3979,8 @@ A [line2](#affineplaneline2) is an object `{ origin: {x,y}, span: {x,y} }` - [affineplane.line2.at](#affineplaneline2at) - [affineplane.line2.create](#affineplaneline2create) - [affineplane.line2.fromPoints](#affineplaneline2frompoints) +- [affineplane.line2.intersection](#affineplaneline2intersection) +- [affineplane.line2.normal](#affineplaneline2normal) - [affineplane.line2.validate](#affineplaneline2validate) @@ -3803,6 +4049,51 @@ with a spanning vector from p to q. Source: [fromPoints.js](https://github.com/axelpale/affineplane/blob/main/lib/line2/fromPoints.js) + +## [affineplane](#affineplane).[line2](#affineplaneline2).[intersection](#affineplaneline2intersection)(l, ll) + +Find intersection point between two lines, if any. +If the lines are parallel but not equal, no intersection exist and +therefore the result is null. +If the lines are equal, while the whole line +would be the true intersection, +the result is the origin point of the first line. + +

Parameters:

+ +- *l* + - a [line2](#affineplaneline2) +- *ll* + - a [line2](#affineplaneline2) + + +

Returns:

+ +- a [point2](#affineplanepoint2) or null + + +Source: [intersection.js](https://github.com/axelpale/affineplane/blob/main/lib/line2/intersection.js) + + +## [affineplane](#affineplane).[line2](#affineplaneline2).[normal](#affineplaneline2normal)(line) + +Get a normal vector for the line. It is perpendicular to the line. +Note that a line has two normal vectors, one for each side. +The returned normal is the one at the right hand. + +

Parameters:

+ +- *line* + - a [line2](#affineplaneline2) + + +

Returns:

+ +- a [vec2](#affineplanevec2), a vector perpendicular to the line. + + +Source: [normal.js](https://github.com/axelpale/affineplane/blob/main/lib/line2/normal.js) + ## [affineplane](#affineplane).[line2](#affineplaneline2).[validate](#affineplaneline2validate)(obj) @@ -3829,7 +4120,7 @@ Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/line Directed line object in 3D, Represented as an origin point and a spanning vector. -A [line2](#affineplaneline2) is an object `{ origin: {x,y,z}, span: {x,y,z} }` +A [line3](#affineplaneline3) is an object `{ origin: {x,y,z}, span: {x,y,z} }`

Contents:

@@ -3838,6 +4129,7 @@ A [line2](#affineplaneline2) is an object `{ origin: {x,y,z}, span: {x,y,z} }` - [affineplane.line3.at](#affineplaneline3at) - [affineplane.line3.create](#affineplaneline3create) - [affineplane.line3.fromPoints](#affineplaneline3frompoints) +- [affineplane.line3.intersection](#affineplaneline3intersection) - [affineplane.line3.validate](#affineplaneline3validate) @@ -3906,6 +4198,31 @@ with a spanning vector from p to q. Source: [fromPoints.js](https://github.com/axelpale/affineplane/blob/main/lib/line3/fromPoints.js) + +## [affineplane](#affineplane).[line3](#affineplaneline3).[intersection](#affineplaneline3intersection)(l, ll) + +Find intersection point between two lines, if any. +If the lines are parallel but not equal, or the lines are skew, +no intersection exist and therefore the result is null. +If the lines are equal, while the whole line +would be the true intersection. However, in this case we pick +the origin point of the first line. + +

Parameters:

+ +- *l* + - a [line3](#affineplaneline3) +- *ll* + - a [line3](#affineplaneline3) + + +

Returns:

+ +- a [point3](#affineplanepoint3) or null + + +Source: [intersection.js](https://github.com/axelpale/affineplane/blob/main/lib/line3/intersection.js) + ## [affineplane](#affineplane).[line3](#affineplaneline3).[validate](#affineplaneline3validate)(line) @@ -4063,6 +4380,7 @@ Example: `[{ x, y }, { x, y }, ...]` - [affineplane.path2.create](#affineplanepath2create) - [affineplane.path2.transitFrom](#affineplanepath2transitfrom) - [affineplane.path2.transitTo](#affineplanepath2transitto) +- [affineplane.path2.validate](#affineplanepath2validate) Source: [path2/index.js](https://github.com/axelpale/affineplane/blob/main/lib/path2/index.js) @@ -4145,6 +4463,25 @@ Transit a path to the target basis. Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/path2/transitTo.js) + +## [affineplane](#affineplane).[path2](#affineplanepath2).[validate](#affineplanepath2validate)(p) + +Check if the object is a valid [path2](#affineplanepath2). +A valid [path2](#affineplanepath2) is an array of valid [point2](#affineplanepoint2) objects. + +

Parameters:

+ +- *p* + - an object + + +

Returns:

+ +- a boolean, true if valid + + +Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/path2/validate.js) + ## [affineplane](#affineplane).[path3](#affineplanepath3) @@ -4163,6 +4500,7 @@ Example: `[{ x, y, z }, { x, y, z }, ...]` - [affineplane.path3.projectToPlane](#affineplanepath3projecttoplane) - [affineplane.path3.transitFrom](#affineplanepath3transitfrom) - [affineplane.path3.transitTo](#affineplanepath3transitto) +- [affineplane.path3.validate](#affineplanepath3validate) Source: [path3/index.js](https://github.com/axelpale/affineplane/blob/main/lib/path3/index.js) @@ -4277,6 +4615,25 @@ Transit a path to the target basis. Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/path3/transitTo.js) + +## [affineplane](#affineplane).[path3](#affineplanepath3).[validate](#affineplanepath3validate)(p) + +Check if the object is a valid [path3](#affineplanepath3). +A valid [path3](#affineplanepath3) is an array of valid [point3](#affineplanepoint3) objects. + +

Parameters:

+ +- *p* + - an object + + +

Returns:

+ +- a boolean, true if valid + + +Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/path3/validate.js) + ## [affineplane](#affineplane).[plane2](#affineplaneplane2) @@ -5788,16 +6145,16 @@ Create `{ x, y }` point from array [x, y]. Source: [fromArray.js](https://github.com/axelpale/affineplane/blob/main/lib/point2/fromArray.js) -## [affineplane](#affineplane).[point2](#affineplanepoint2).[homothety](#affineplanepoint2homothety)(point, center, ratio) +## [affineplane](#affineplane).[point2](#affineplanepoint2).[homothety](#affineplanepoint2homothety)(point, origin, ratio) -Perform homothety about the center. +Perform homothety about the origin point.

Parameters:

- *point* - a [point2](#affineplanepoint2) -- *center* - - a [point2](#affineplanepoint2) +- *origin* + - a [point2](#affineplanepoint2), the pivot point - *ratio* - a number, the scaling ratio @@ -6047,6 +6404,7 @@ Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/poi ## [affineplane](#affineplane).[point2](#affineplanepoint2).[translate](#affineplanepoint2translate)(p, vec) Translate the point by the given vector. +See [affineplane.point2.offset](#affineplanepoint2offset) to translate by scalars.

Parameters:

@@ -6114,6 +6472,7 @@ translation of the plane on which they are represented. - [affineplane.point3.equal](#affineplanepoint3equal) - [affineplane.point3.equals](#affineplanepoint3equals) - [affineplane.point3.fromArray](#affineplanepoint3fromarray) +- [affineplane.point3.homothety](#affineplanepoint3homothety) - [affineplane.point3.mean](#affineplanepoint3mean) - [affineplane.point3.offset](#affineplanepoint3offset) - [affineplane.point3.polarOffset](#affineplanepoint3polaroffset) @@ -6321,22 +6680,44 @@ Create a [point3](#affineplanepoint3) from array `[x, y, z]`. Source: [fromArray.js](https://github.com/axelpale/affineplane/blob/main/lib/point3/fromArray.js) - -## [affineplane](#affineplane).[point3](#affineplanepoint3).[mean](#affineplanepoint3mean) - -Alias of [affineplane.point3.average](#affineplanepoint3average) - -Source: [average.js](https://github.com/axelpale/affineplane/blob/main/lib/point3/average.js) - - -## [affineplane](#affineplane).[point3](#affineplanepoint3).[offset](#affineplanepoint3offset)(p, dx, dy, dz) + +## [affineplane](#affineplane).[point3](#affineplanepoint3).[homothety](#affineplanepoint3homothety)(point, origin, ratio) -Offset a point by scalars dx, dy, dz. +Perform a homothety about the origin point.

Parameters:

-- *p* - - a [point2](#affineplanepoint2) +- *point* + - a [point3](#affineplanepoint3) +- *origin* + - a [point3](#affineplanepoint3) +- *ratio* + - a number + + +

Returns:

+ +- a [point3](#affineplanepoint3) + + +Source: [homothety.js](https://github.com/axelpale/affineplane/blob/main/lib/point3/homothety.js) + + +## [affineplane](#affineplane).[point3](#affineplanepoint3).[mean](#affineplanepoint3mean) + +Alias of [affineplane.point3.average](#affineplanepoint3average) + +Source: [average.js](https://github.com/axelpale/affineplane/blob/main/lib/point3/average.js) + + +## [affineplane](#affineplane).[point3](#affineplanepoint3).[offset](#affineplanepoint3offset)(p, dx, dy, dz) + +Offset a point by scalars dx, dy, dz. + +

Parameters:

+ +- *p* + - a [point2](#affineplanepoint2) - *dx* - a number, the offset along x-axis - *dy* @@ -7186,6 +7567,7 @@ nor the representation. - [affineplane.scalar1.equal](#affineplanescalar1equal) - [affineplane.scalar1.transitFrom](#affineplanescalar1transitfrom) - [affineplane.scalar1.transitTo](#affineplanescalar1transitto) +- [affineplane.scalar1.validate](#affineplanescalar1validate) Source: [scalar1/index.js](https://github.com/axelpale/affineplane/blob/main/lib/scalar1/index.js) @@ -7298,6 +7680,25 @@ in the coordinate system of the target. Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/scalar1/transitTo.js) + +## [affineplane](#affineplane).[scalar1](#affineplanescalar1).[validate](#affineplanescalar1validate)(s) + +Check if the argument is a valid scalar1. +Valid scalar1 is a number and not NaN. + +

Parameters:

+ +- *s* + - a value + + +

Returns:

+ +- a boolean, true if valid scalar1 + + +Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/scalar1/validate.js) + ## [affineplane](#affineplane).[scalar2](#affineplanescalar2) @@ -7322,6 +7723,7 @@ On the map, an area of 1 squaremeter of ground is represented by - [affineplane.scalar2.equal](#affineplanescalar2equal) - [affineplane.scalar2.transitFrom](#affineplanescalar2transitfrom) - [affineplane.scalar2.transitTo](#affineplanescalar2transitto) +- [affineplane.scalar2.validate](#affineplanescalar2validate) Source: [scalar2/index.js](https://github.com/axelpale/affineplane/blob/main/lib/scalar2/index.js) @@ -7435,6 +7837,25 @@ in the coordinate system of the target. Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/scalar2/transitTo.js) + +## [affineplane](#affineplane).[scalar2](#affineplanescalar2).[validate](#affineplanescalar2validate)(s) + +Check if the argument is a valid scalar2. +Valid scalar2 is a number and not NaN. + +

Parameters:

+ +- *s* + - a value + + +

Returns:

+ +- a boolean, true if valid scalar2 + + +Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/scalar2/validate.js) + ## [affineplane](#affineplane).[scalar3](#affineplanescalar3) @@ -7460,6 +7881,7 @@ In the miniature, the volume of 1 cubic meter is represented by - [affineplane.scalar3.equal](#affineplanescalar3equal) - [affineplane.scalar3.transitFrom](#affineplanescalar3transitfrom) - [affineplane.scalar3.transitTo](#affineplanescalar3transitto) +- [affineplane.scalar3.validate](#affineplanescalar3validate) Source: [scalar3/index.js](https://github.com/axelpale/affineplane/blob/main/lib/scalar3/index.js) @@ -7573,6 +7995,147 @@ in the coordinate system of the target. Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/scalar3/transitTo.js) + +## [affineplane](#affineplane).[scalar3](#affineplanescalar3).[validate](#affineplanescalar3validate)(s) + +Check if the argument is a valid scalar3. +Valid scalar3 is a number and not NaN. + +

Parameters:

+ +- *s* + - a value + + +

Returns:

+ +- a boolean, true if valid scalar3 + + +Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/scalar3/validate.js) + + +## [affineplane](#affineplane).[segment2](#affineplanesegment2) + +Two-dimensional line segment. Represented by the segment start and end +points in an array of length two. + +Example: `[{ x: 0, y: 0 }, { x: 1, y: 2 }]` + + +

Contents:

+ + +- [affineplane.segment2.collide](#affineplanesegment2collide) +- [affineplane.segment2.create](#affineplanesegment2create) +- [affineplane.segment2.transitFrom](#affineplanesegment2transitfrom) +- [affineplane.segment2.transitTo](#affineplanesegment2transitto) +- [affineplane.segment2.validate](#affineplanesegment2validate) + + +Source: [segment2/index.js](https://github.com/axelpale/affineplane/blob/main/lib/segment2/index.js) + + +## [affineplane](#affineplane).[segment2](#affineplanesegment2).[collide](#affineplanesegment2collide)(s0, s1) + +Test if two segments collide. + +

Parameters:

+ +- *s0* + - a segment2 +- *s1* + - a segment2 + + +

Returns:

+ +- a boolean, true if segments collide. + + +Source: [collide.js](https://github.com/axelpale/affineplane/blob/main/lib/segment2/collide.js) + + +## [affineplane](#affineplane).[segment2](#affineplanesegment2).[create](#affineplanesegment2create)(p0, p1) + +Create a segment from points. + +

Parameters:

+ +- *p0* + - a [point2](#affineplanepoint2) +- *p1* + - a [point2](#affineplanepoint2) + + +

Returns:

+ +- a segment2, an array. + + +Source: [create.js](https://github.com/axelpale/affineplane/blob/main/lib/segment2/create.js) + + +## [affineplane](#affineplane).[segment2](#affineplanesegment2).[transitFrom](#affineplanesegment2transitfrom)(seg, source) + +Represent a segment on the reference basis. In other words, +transit the segment from the source basis to the reference basis. + +

Parameters:

+ +- *seg* + - a segment2, represented on the source basis. +- *source* + - a [plane2](#affineplaneplane2), the source basis, represented on the reference basis. + + +

Returns:

+ +- a segment2, represented on the reference basis. + + +Source: [transitFrom.js](https://github.com/axelpale/affineplane/blob/main/lib/segment2/transitFrom.js) + + +## [affineplane](#affineplane).[segment2](#affineplanesegment2).[transitTo](#affineplanesegment2transitto)(seg, target) + +Represent a segment in the target basis. In other words, +transit the segment from the reference basis to the target basis. + +

Parameters:

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

Returns:

+ +- a segment2, represented in the target basis. + + +Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/segment2/transitTo.js) + + +## [affineplane](#affineplane).[segment2](#affineplanesegment2).[validate](#affineplanesegment2validate)(seg) + +Check if the object is a valid segment2. +A valid segment2 is an array of two valid [point2](#affineplanepoint2) objects. + +

Parameters:

+ +- *seg* + - an object + + +

Returns:

+ +- a boolean, true if valid + + +Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/segment2/validate.js) + ## [affineplane](#affineplane).[size2](#affineplanesize2) @@ -7907,6 +8470,634 @@ the total number of voxels. Source: [volume.js](https://github.com/axelpale/affineplane/blob/main/lib/size3/volume.js) + +## [affineplane](#affineplane).[sphere2](#affineplanesphere2) + +Two dimensional sphere, a circle. + +Represented with an object `{ x, y, r }` for the origin and the radius. + + +

Contents:

+ + +- [affineplane.sphere2.almostEqual](#affineplanesphere2almostequal) +- [affineplane.sphere2.area](#affineplanesphere2area) +- [affineplane.sphere2.collide](#affineplanesphere2collide) +- [affineplane.sphere2.copy](#affineplanesphere2copy) +- [affineplane.sphere2.create](#affineplanesphere2create) +- [affineplane.sphere2.gap](#affineplanesphere2gap) +- [affineplane.sphere2.hasPoint](#affineplanesphere2haspoint) +- [affineplane.sphere2.homothety](#affineplanesphere2homothety) +- [affineplane.sphere2.offset](#affineplanesphere2offset) +- [affineplane.sphere2.scaleBy](#affineplanesphere2scaleby) +- [affineplane.sphere2.transitFrom](#affineplanesphere2transitfrom) +- [affineplane.sphere2.transitTo](#affineplanesphere2transitto) +- [affineplane.sphere2.validate](#affineplanesphere2validate) + + +Source: [sphere2/index.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/index.js) + + +## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[almostEqual](#affineplanesphere2almostequal)(c, d[, tolerance]) + +Test if two spheres are almost equal by the margin of tolerance. + +

Parameters:

+ +- *c* + - a sphere2 +- *d* + - a sphere2 +- *tolerance* + - optional number, default to [affineplane.epsilon](#affineplaneepsilon). Set to 0 for strict comparison. + + +

Returns:

+ +- a boolean + + +Source: [almostEqual.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/almostEqual.js) + + +## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[area](#affineplanesphere2area)(sp) + +Get area of the sphere. + +

Parameters:

+ +- a sphere2 + + +

Returns:

+ +- a scalar2, a number + + +Source: [area.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/area.js) + + +## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[collide](#affineplanesphere2collide)(c, cc) + +Detect collision between two spheres. + +

Parameters:

+ +- *c* + - a sphere2 +- *cc* + - a sphere2 + + +

Returns:

+ +- boolean, true if the spheres collide + + +Source: [collide.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/collide.js) + + +## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[copy](#affineplanesphere2copy)(p) + +Copy a sphere object. + +

Parameters:

+ +- *p* + - a sphere2 + + +

Returns:

+ +- a sphere2 + + +Source: [copy.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/copy.js) + + +## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[create](#affineplanesphere2create)(x, y, r) + +Create a sphere2 object. + +

Parameters:

+ +- *x* + - a number +- *y* + - a number +- *r* + - a number, the radius + + +

Returns:

+ +- a sphere2 + + +Source: [create.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/create.js) + + +## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[gap](#affineplanesphere2gap)(c, cc) + +The gap distance between the two spheres is the closest distance between +their surfaces. The gap is zero or negative when the spheres collide. + +

Parameters:

+ +- *c* + - a sphere2 +- *cc* + - a sphere2 or a [point2](#affineplanepoint2) + + +

Returns:

+ +- a scalar1, a number, the closest distance between the sphere surfaces. + + +Source: [gap.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/gap.js) + + +## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[hasPoint](#affineplanesphere2haspoint)(c, point) + +Detect collision between a sphere and a point. + +

Parameters:

+ +- *c* + - a sphere2 +- *point* + - a [point2](#affineplanepoint2) + + +

Returns:

+ +- boolean, true if the point is inside the sphere or at the surface. + + +Source: [hasPoint.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/hasPoint.js) + + +## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[homothety](#affineplanesphere2homothety)(sphere, origin, ratio) + +Perform homothety for the sphere about a pivot. +In other words, scale the sphere by the given ratio, +so that the pivot point stays fixed. + +

Parameters:

+ +- *sphere* + - a sphere2 +- *origin* + - a [point2](#affineplanepoint2), the transform origin, the pivot point +- *ratio* + - a number, the scaling ratio + + +

Returns:

+ +- a sphere2 + + +Aliases: [affineplane.sphere2.scaleBy](#affineplanesphere2scaleby) + +Source: [homothety.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/homothety.js) + + +## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[offset](#affineplanesphere2offset)(c, dx, dy) + +Offset a sphere by scalars dx, dy. +See [affineplane.sphere2](#affineplanesphere2).translate to offset by a vector. + +

Parameters:

+ +- *c* + - a sphere2 +- *dx* + - a number, an offset along x-axis. +- *dy* + - a number, an offset along y-axis. + + +

Returns:

+ +- a sphere2, translated + + +Source: [offset.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/offset.js) + + +## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[scaleBy](#affineplanesphere2scaleby) + +Alias of [affineplane.sphere2.homothety](#affineplanesphere2homothety) + +Source: [homothety.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/homothety.js) + + +## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[transitFrom](#affineplanesphere2transitfrom)(sphere, source) + +Transit a sphere2 from the source plane +to the reference plane. + +

Parameters:

+ +- *sphere* + - a sphere2 on the source plane. +- *source* + - a [plane2](#affineplaneplane2), the source plane, represented on the reference plane. + + +

Returns:

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

Parameters:

+ +- *sphere* + - a sphere2 on the reference plane. +- *target* + - a [plane2](#affineplaneplane2), the target plane, represented on the reference plane. + + +

Returns:

+ +- a sphere2, represented on the target plane. + + +Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/transitTo.js) + + +## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[validate](#affineplanesphere2validate)(p) + +Check if the object is a valid sphere2. +A valid sphere2 has x, y, r properties that are valid numbers. + +

Parameters:

+ +- *p* + - an object + + +

Returns:

+ +- a boolean, true if valid sphere2 + + +Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/validate.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3) + +Three dimensional sphere. + +Represented with an object `{ x, y, z, r }` for the origin and the radius. + + +

Contents:

+ + +- [affineplane.sphere3.almostEqual](#affineplanesphere3almostequal) +- [affineplane.sphere3.area](#affineplanesphere3area) +- [affineplane.sphere3.collide](#affineplanesphere3collide) +- [affineplane.sphere3.copy](#affineplanesphere3copy) +- [affineplane.sphere3.create](#affineplanesphere3create) +- [affineplane.sphere3.gap](#affineplanesphere3gap) +- [affineplane.sphere3.hasPoint](#affineplanesphere3haspoint) +- [affineplane.sphere3.homothety](#affineplanesphere3homothety) +- [affineplane.sphere3.offset](#affineplanesphere3offset) +- [affineplane.sphere3.projectTo](#affineplanesphere3projectto) +- [affineplane.sphere3.projectToPlane](#affineplanesphere3projecttoplane) +- [affineplane.sphere3.scaleBy](#affineplanesphere3scaleby) +- [affineplane.sphere3.transitFrom](#affineplanesphere3transitfrom) +- [affineplane.sphere3.transitTo](#affineplanesphere3transitto) +- [affineplane.sphere3.validate](#affineplanesphere3validate) +- [affineplane.sphere3.volume](#affineplanesphere3volume) + + +Source: [sphere3/index.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/index.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[almostEqual](#affineplanesphere3almostequal)(c, d[, tolerance]) + +Test if two spheres are almost equal by the margin of tolerance. + +

Parameters:

+ +- *c* + - a sphere3 +- *d* + - a sphere3 +- *tolerance* + - optional number, default to [affineplane.epsilon](#affineplaneepsilon). Set to 0 for strict comparison. + + +

Returns:

+ +- a boolean + + +Source: [almostEqual.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/almostEqual.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[area](#affineplanesphere3area)(sp) + +Get surface area of the sphere. + +

Parameters:

+ +- a sphere3 + + +

Returns:

+ +- a scalar2, a number. The area. + + +Source: [area.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/area.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[collide](#affineplanesphere3collide)(c, cc) + +Detect collision between two spheres. + +

Parameters:

+ +- *c* + - a sphere3 +- *cc* + - a sphere3 + + +

Returns:

+ +- boolean, true if the spheres collide + + +Source: [collide.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/collide.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[copy](#affineplanesphere3copy)(p) + +Copy a sphere object. + +

Parameters:

+ +- *p* + - a sphere3 + + +

Returns:

+ +- a sphere3 + + +Source: [copy.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/copy.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[create](#affineplanesphere3create)(x, y, z, r) + +Create a sphere3 object. + +

Parameters:

+ +- *x* + - a number +- *y* + - a number +- *z* + - a number +- *r* + - a number, the radius + + +

Returns:

+ +- a sphere3 + + +Source: [create.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/create.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[gap](#affineplanesphere3gap)(c, cc) + +The gap distance between the two spheres is the closest distance between +their surfaces. The gap is zero or negative when the spheres collide. + +

Parameters:

+ +- *c* + - a sphere3 +- *cc* + - a sphere3 or a [point3](#affineplanepoint3) + + +

Returns:

+ +- a scalar1, a number, the closest distance between the sphere surfaces. + + +Source: [gap.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/gap.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[hasPoint](#affineplanesphere3haspoint)(c, point) + +Detect collision between a sphere and a point. + +

Parameters:

+ +- *c* + - a sphere3 +- *point* + - a [point2](#affineplanepoint2) + + +

Returns:

+ +- boolean, true if the point is inside the sphere or at the surface. + + +Source: [hasPoint.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/hasPoint.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[homothety](#affineplanesphere3homothety)(sphere, origin, ratio) + +Perform homothety for the sphere about a pivot. +In other words, scale the sphere by the given ratio, +so that the pivot point stays fixed. + +

Parameters:

+ +- *sphere* + - a sphere3 +- *origin* + - a [point3](#affineplanepoint3), the transform origin, the pivot point +- *ratio* + - a number, the scaling ratio + + +

Returns:

+ +- a sphere3 + + +Aliases: [affineplane.sphere3.scaleBy](#affineplanesphere3scaleby) + +Source: [homothety.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/homothety.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[offset](#affineplanesphere3offset)(c, dx, dy[, dz]) + +Offset a sphere by scalars dx, dy, dz. +See [affineplane.sphere3](#affineplanesphere3).translate to offset by a vector. + +

Parameters:

+ +- *c* + - a sphere3 +- *dx* + - a number, an offset along x-axis. +- *dy* + - a number, an offset along y-axis. +- *dz* + - optional number. The offset along z-axis, default is 0. + + +

Returns:

+ +- a sphere3, translated + + +Source: [offset.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/offset.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[projectTo](#affineplanesphere3projectto) + +Alias of [affineplane.sphere3.projectToPlane](#affineplanesphere3projecttoplane) + +Source: [projectToPlane.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/projectToPlane.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[projectToPlane](#affineplanesphere3projecttoplane)(sphere, plane[, camera]) + +Project a 3D sphere onto a plane in 3D space. The result is a 2D sphere. +If the camera is undefined, project orthogonally. + +

Parameters:

+ +- *sphere* + - a sphere3 in the reference space. +- *plane* + - a [plane3](#affineplaneplane3) in the reference space. The target plane. +- *camera* + - optional [point3](#affineplanepoint3) in the reference space. The camera position. + + +

Returns:

+ +- a sphere2 on the target plane. + + +Aliases: [affineplane.sphere3.projectTo](#affineplanesphere3projectto) + +Source: [projectToPlane.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/projectToPlane.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[scaleBy](#affineplanesphere3scaleby) + +Alias of [affineplane.sphere3.homothety](#affineplanesphere3homothety) + +Source: [homothety.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/homothety.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[transitFrom](#affineplanesphere3transitfrom)(sphere, source) + +Transit a sphere3 from the source plane +to the reference plane. + +

Parameters:

+ +- *sphere* + - a sphere3 on the source plane. +- *source* + - a [plane3](#affineplaneplane3), the source plane, represented on the reference plane. + + +

Returns:

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

Parameters:

+ +- *sphere* + - a sphere3 on the reference plane. +- *target* + - a [plane3](#affineplaneplane3), the target plane, represented on the reference plane. + + +

Returns:

+ +- a sphere3, represented on the target plane. + + +Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/transitTo.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[validate](#affineplanesphere3validate)(p) + +Check if the object is a valid sphere3. +A valid sphere3 has x, y, z, r properties that are valid numbers. + +

Parameters:

+ +- *p* + - an object + + +

Returns:

+ +- a boolean, true if valid sphere3 + + +Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/validate.js) + + +## [affineplane](#affineplane).[sphere3](#affineplanesphere3).[volume](#affineplanesphere3volume)(sp) + +Get volume of the sphere. + +

Parameters:

+ +- a sphere3 + + +

Returns:

+ +- a scalar3, a number. The volume. + + +Source: [volume.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere3/volume.js) + ## [affineplane](#affineplane).[vec2](#affineplanevec2) From 16350a4b16243a64bc749f26f20df884bd9adb33 Mon Sep 17 00:00:00 2001 From: Akseli Palen Date: Sat, 11 Mar 2023 01:29:52 +0200 Subject: [PATCH 36/36] bump v2.12.0 --- 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 d0c5b5d4..90230679 100644 --- a/lib/version.js +++ b/lib/version.js @@ -1,2 +1,2 @@ // Generated by genversion. -module.exports = '2.11.0' +module.exports = '2.12.0' diff --git a/package.json b/package.json index d5cc5e2a..4295d4e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "affineplane", - "version": "2.11.0", + "version": "2.12.0", "description": "Affine plane geometry library", "keywords": [ "affine",