@papit/quaternion 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,138 @@
1
- # Temporary Holding Version
1
+ # @papit/quaternion
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Quaternion math for 3D rotations that compose and blend without gimbal lock. Reach for it when Euler angles fail in cameras, bones or animation.
4
+
5
+ ![Logo](https://raw.githubusercontent.com/onkelhoy/papit/refs/heads/main/asset/logo.svg)
6
+
7
+ ---
8
+
9
+ ![Type](https://img.shields.io/badge/Type-math:algebra-orange)
10
+ [![Tests](https://github.com/onkelhoy/papit/actions/workflows/pull-request.yml/badge.svg)](https://github.com/onkelhoy/papit/actions/workflows/pull-request.yml)
11
+ [![NPM version](https://img.shields.io/npm/v/@papit/quaternion.svg?logo=npm)](https://www.npmjs.com/package/@papit/quaternion)
12
+
13
+ ---
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @papit/quaternion
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```js
24
+ import { Quaternion } from "@papit/quaternion";
25
+
26
+ const turn = Quaternion.fromAxisAngle([0, 0, 1], Math.PI / 2);
27
+ turn.rotate([1, 0, 0]); // about [0, 1, 0]
28
+ ```
29
+
30
+ Combine rotations, `b` runs first:
31
+
32
+ ```js
33
+ import { Quaternion } from "@papit/quaternion";
34
+
35
+ const a = Quaternion.fromAxisAngle([0, 0, 1], .5);
36
+ const b = Quaternion.fromAxisAngle([1, 0, 0], .25);
37
+
38
+ const both = Quaternion.multiplyInto(Quaternion.identity, a, b);
39
+ ```
40
+
41
+ Aim one direction at another:
42
+
43
+ ```js
44
+ import { Quaternion } from "@papit/quaternion";
45
+
46
+ const aim = Quaternion.fromTo([1, 0, 0], [0, 3, 4]);
47
+ ```
48
+
49
+ Blend two rotations and hand the result to a `Matrix4`:
50
+
51
+ ```js
52
+ import { Quaternion } from "@papit/quaternion";
53
+ import { Matrix4 } from "@papit/matrix";
54
+
55
+ const from = Quaternion.identity;
56
+ const to = Quaternion.fromAxisAngle([0, 1, 0], 2);
57
+
58
+ const matrix = from.slerp(to, .5).writeMatrix(new Matrix4(), [10, 0, 0]);
59
+ ```
60
+
61
+ ## API
62
+
63
+ ### Quaternion
64
+
65
+ ```ts
66
+ class Quaternion extends Vector
67
+ ```
68
+
69
+ Stored as x, y, z, w in a `Float32Array`. Everything below is O(1). Instance methods change the quaternion and return it, except `rotate` and `writeMatrix`, which return their target.
70
+
71
+ #### Create
72
+
73
+ | Member | Purpose |
74
+ | --- | --- |
75
+ | `Quaternion.identity` | A new quaternion with no rotation. |
76
+ | `Quaternion.fromAxisAngle(axis, angle)` | A turn in radians around an axis of any length. |
77
+ | `Quaternion.fromTo(from, to)` | The shortest rotation from one direction to another. |
78
+ | `identity()` | Resets to no rotation. |
79
+ | `setAxisAngle(x, y, z, angle)` | `fromAxisAngle` without allocating. |
80
+ | `setFromTo(from, to)` | `fromTo` without allocating. |
81
+
82
+ #### Combine
83
+
84
+ | Member | Purpose |
85
+ | --- | --- |
86
+ | `Quaternion.multiplyInto(out, a, b)` | Writes `a * b`, which applies `b` first. `out` can be `a` or `b`. |
87
+ | `conjugate()` | Flips to the inverse, for a unit quaternion. |
88
+ | `normalize()` | Scales to length 1. Do this now and then after many products. |
89
+ | `rotate(point)` | Rotates a 3D point in place. |
90
+
91
+ #### Blend
92
+
93
+ | Member | Purpose |
94
+ | --- | --- |
95
+ | `nlerp(target, t)` | Fast blend, the speed isn't constant over large angles. |
96
+ | `slerp(target, t)` | Blend at a constant speed. |
97
+
98
+ Both take the short way round and return a unit quaternion.
99
+
100
+ #### Output
101
+
102
+ ```ts
103
+ writeMatrix(out: Float32Array, position?: ArrayLike<number>)
104
+ ```
105
+
106
+ Writes the rotation and position into a column-major 4x4 matrix, such as a `Matrix4`.
107
+
108
+ ### Degenerate input
109
+
110
+ | Input | Result |
111
+ | --- | --- |
112
+ | Zero axis or zero vector | The identity |
113
+ | Zero quaternion in `normalize` | The identity |
114
+ | Opposite directions in `fromTo` | A half turn around a perpendicular axis |
115
+
116
+ ## Design notes
117
+
118
+ - `rotate`, `conjugate`, `nlerp`, `slerp` and `writeMatrix` expect a unit quaternion, which everything in Create gives you.
119
+ - `multiply` and `lerp` are inherited from `Vector` and work per component. Use `multiplyInto` to combine rotations and `nlerp` or `slerp` to blend them.
120
+ - Instance methods and `multiplyInto` don't allocate, so they're safe in a frame loop. The static `identity`, `fromAxisAngle` and `fromTo` create a new quaternion.
121
+
122
+ ## License
123
+
124
+ Licensed under the @Papit License 1.0 - Copyright (c) 2024 Henry Pap (@onkelhoy)
125
+
126
+ **Key points:**
127
+
128
+ - ✅ Free to use in commercial projects
129
+ - ✅ Free to modify and distribute
130
+ - ✅ Attribution required
131
+ - ❌ Cannot resell the component itself as a standalone product
132
+
133
+ See the [LICENSE](https://github.com/onkelhoy/papit/blob/main/LICENSE) file for full details.
134
+
135
+ ## Related
136
+
137
+ - [@papit/vector](https://github.com/onkelhoy/papit/tree/main/packages/algorithms/math/algebra/vector)
138
+ - [@papit/matrix](https://github.com/onkelhoy/papit/tree/main/packages/algorithms/math/algebra/matrix)
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "region": "GB",
4
+ "language": "en"
5
+ }
6
+ }
@@ -0,0 +1,48 @@
1
+ import { Vector } from '@papit/vector';
2
+
3
+ declare type Point = ArrayLike<number>;
4
+
5
+ /**
6
+ * A 3D rotation stored as x, y, z, w, where a unit quaternion is
7
+ * `axis * sin(angle / 2), cos(angle / 2)`.
8
+ * Create one with `Quaternion.identity`, `fromAxisAngle` or `fromTo`.
9
+ * `multiply` and `lerp` are still Vector's component-wise ones,
10
+ * use `multiplyInto` to combine rotations and `nlerp` or `slerp` to blend them.
11
+ */
12
+ export declare class Quaternion extends Vector {
13
+ /** Sets this to no rotation. */
14
+ identity(): this;
15
+ /** Scales to length 1. A zero quaternion isn't a rotation, so it becomes the identity. */
16
+ normalize(): this;
17
+ /** Sets this to a turn of `angle` radians around an axis of any length. A zero axis gives the identity. */
18
+ setAxisAngle(x: number, y: number, z: number, angle: number): this;
19
+ /**
20
+ * Sets this to the shortest rotation that turns direction `from` onto direction `to`.
21
+ * A zero vector has no direction, so it gives the identity.
22
+ */
23
+ setFromTo(from: Point, to: Point): this;
24
+ /** Flips this to its inverse, which undoes a unit quaternion. */
25
+ conjugate(): this;
26
+ /** Rotates a 3D point in place and returns it. This has to be a unit quaternion. */
27
+ rotate<T extends Float32Array | number[]>(point: T): T;
28
+ /** Moves this towards `target` the short way, normalized: 0 stays, 1 lands on it. Fast, a good fit for small steps. */
29
+ nlerp(target: Quaternion, t: number): this;
30
+ /** Moves this towards `target` the short way at a constant speed: 0 stays, 1 lands on it. */
31
+ slerp(target: Quaternion, t: number): this;
32
+ /** Writes this rotation and an optional position into a column-major 4x4 matrix. This has to be a unit quaternion. */
33
+ writeMatrix<T extends Float32Array>(out: T, position?: Point): T;
34
+ /**
35
+ * A half turn around the axis `x, y, z` leans on the least, made perpendicular to `x, y, z`.
36
+ * For a direction in a plane that's the plane's normal, so 2D stays 2D.
37
+ */
38
+ private setHalfTurn;
39
+ /** Writes the Hamilton product `a * b`, which applies `b` first and then `a`. `out` can be `a` or `b`. */
40
+ static multiplyInto<T extends Float32Array>(out: T, a: Float32Array, b: Float32Array): T;
41
+ /** A turn of `angle` radians around an axis of any length. */
42
+ static fromAxisAngle(axis: Point, angle: number): Quaternion;
43
+ /** The shortest rotation that turns direction `from` onto direction `to`. */
44
+ static fromTo(from: Point, to: Point): Quaternion;
45
+ static get identity(): Quaternion;
46
+ }
47
+
48
+ export { }
package/lib/bundle.js ADDED
@@ -0,0 +1 @@
1
+ import{Vector as u}from"@papit/vector";var y=1e-6,d=.9995,m=class b extends u{identity(){return this[0]=0,this[1]=0,this[2]=0,this[3]=1,this}normalize(){let t=Math.hypot(this[0],this[1],this[2],this[3]);return t===0?this.identity():(this[0]/=t,this[1]/=t,this[2]/=t,this[3]/=t,this)}setAxisAngle(t,i,s,h){let n=Math.hypot(t,i,s);if(n===0)return this.identity();let r=Math.sin(h/2)/n;return this[0]=t*r,this[1]=i*r,this[2]=s*r,this[3]=Math.cos(h/2),this}setFromTo(t,i){let s=t[0],h=t[1],n=t[2],r=i[0],e=i[1],c=i[2],a=Math.hypot(s,h,n)*Math.hypot(r,e,c);if(a===0)return this.identity();let o=a+s*r+h*e+n*c;return o<y*a?this.setHalfTurn(s,h,n):(this[0]=h*c-n*e,this[1]=n*r-s*c,this[2]=s*e-h*r,this[3]=o,this.normalize())}conjugate(){return this[0]=-this[0],this[1]=-this[1],this[2]=-this[2],this}rotate(t){let i=this[0],s=this[1],h=this[2],n=this[3],r=t[0],e=t[1],c=t[2],a=2*(s*c-h*e),o=2*(h*r-i*c),l=2*(i*e-s*r);return t[0]=r+n*a+s*l-h*o,t[1]=e+n*o+h*a-i*l,t[2]=c+n*l+i*o-s*a,t}nlerp(t,i){let s=this.dot(t)<0?-1:1;return this[0]+=(t[0]*s-this[0])*i,this[1]+=(t[1]*s-this[1])*i,this[2]+=(t[2]*s-this[2])*i,this[3]+=(t[3]*s-this[3])*i,this.normalize()}slerp(t,i){let s=this.dot(t),h=s<0?-1:1,n=s*h;if(n>d)return this.nlerp(t,i);let r=Math.acos(n),e=Math.sin((1-i)*r),c=Math.sin(i*r)*h;return this[0]=this[0]*e+t[0]*c,this[1]=this[1]*e+t[1]*c,this[2]=this[2]*e+t[2]*c,this[3]=this[3]*e+t[3]*c,this.normalize()}writeMatrix(t,i){let s=this[0],h=this[1],n=this[2],r=this[3];return t[0]=1-2*(h*h+n*n),t[1]=2*(s*h+n*r),t[2]=2*(s*n-h*r),t[3]=0,t[4]=2*(s*h-n*r),t[5]=1-2*(s*s+n*n),t[6]=2*(h*n+s*r),t[7]=0,t[8]=2*(s*n+h*r),t[9]=2*(h*n-s*r),t[10]=1-2*(s*s+h*h),t[11]=0,t[12]=i?i[0]:0,t[13]=i?i[1]:0,t[14]=i?i[2]:0,t[15]=1,t}setHalfTurn(t,i,s){let h=Math.abs(t),n=Math.abs(i),r=Math.abs(s),e=(r<=h&&r<=n?s:n<=h?i:t)/(t*t+i*i+s*s);return this.setAxisAngle((r>h&&n>h?1:0)-t*e,(n<=h&&n<r?1:0)-i*e,(r<=h&&r<=n?1:0)-s*e,Math.PI)}static multiplyInto(t,i,s){let h=i[0],n=i[1],r=i[2],e=i[3],c=s[0],a=s[1],o=s[2],l=s[3];return t[0]=e*c+h*l+n*o-r*a,t[1]=e*a-h*o+n*l+r*c,t[2]=e*o+h*a-n*c+r*l,t[3]=e*l-h*c-n*a-r*o,t}static fromAxisAngle(t,i){return b.identity.setAxisAngle(t[0],t[1],t[2],i)}static fromTo(t,i){return b.identity.setFromTo(t,i)}static get identity(){return new b(0,0,0,1)}};export{m as Quaternion};
package/package.json CHANGED
@@ -1,6 +1,80 @@
1
1
  {
2
2
  "name": "@papit/quaternion",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "description": "Quaternion math for 3D rotations that compose and blend without gimbal lock. Reach for it when Euler angles fail in cameras, bones or animation.",
4
+ "keywords": [
5
+ "papit",
6
+ "quaternion",
7
+ "rotation",
8
+ "3d",
9
+ "math",
10
+ "game"
11
+ ],
12
+ "version": "0.0.1",
13
+ "scripts": {
14
+ "build": "npx @papit/build -- --error --ancestors",
15
+ "watch": "tsc -w",
16
+ "start": "npm run build -- --dev && node ./lib/bundle.js",
17
+ "test": "node --test tests/**/*.test.js",
18
+ "test:only": "node --test --test-only tests/**/*.test.js",
19
+ "preversion": "papit-version",
20
+ "postversion": "papit-version",
21
+ "component": "npm create @papit -- --component"
22
+ },
23
+ "main": "./lib/bundle.js",
24
+ "types": "./lib/bundle.d.ts",
25
+ "type": "module",
26
+ "files": [
27
+ "asset/",
28
+ "lib/"
29
+ ],
30
+ "exports": {
31
+ ".": {
32
+ "import": "./lib/bundle.js",
33
+ "types": "./lib/bundle.d.ts"
34
+ }
35
+ },
36
+ "author": "henry",
37
+ "private": false,
38
+ "publishConfig": {
39
+ "access": "public",
40
+ "registry": "https://registry.npmjs.org/"
41
+ },
42
+ "license": "LicenseRef-Papit-1.0",
43
+ "homepage": "https://github.com/onkelhoy/papit/tree/main/packages/algorithms/math/algebra/quaternion",
44
+ "bugs": {
45
+ "url": "https://github.com/onkelhoy/papit/issues"
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/onkelhoy/papit.git",
50
+ "directory": "packages/algorithms/math/algebra/quaternion"
51
+ },
52
+ "sideEffects": false,
53
+ "engines": {
54
+ "node": ">=18"
55
+ },
56
+ "dependencies": {
57
+ "@papit/vector": "0.0.2"
58
+ },
59
+ "devDependencies": {
60
+ "@papit/matrix": "0.0.2",
61
+ "@types/node": "22.10.2",
62
+ "typescript": "5.9.3"
63
+ },
64
+ "browserslist": [
65
+ "defaults",
66
+ "not IE 11",
67
+ "not dead"
68
+ ],
69
+ "papit": {
70
+ "publish": true,
71
+ "type": "node",
72
+ "main": "quaternion",
73
+ "components": {
74
+ "quaternion": {
75
+ "className": "Quaternion",
76
+ "htmlprefix": "pap"
77
+ }
78
+ }
79
+ }
6
80
  }