Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add units, add w prop #15

Merged
merged 12 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .prettierrc.cjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use strict'
'use strict';

module.exports = '@viamrobotics/prettier-config';
/* eslint-disable @typescript-eslint/no-unsafe-call */
module.exports = require('@viamrobotics/prettier-config');
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
# @viamrobotics/three

Viam-related utilities for THREE.js

### Orientation Vector

A class for Viam's [Orientation Vector](https://docs.viam.com/internals/orientation-vector/#edit-on-github) rotation type.

This class closely resembles other rotation formats like `THREE.Quaternion` or `THREE.Euler`.

```ts
import * as THREE from 'three'
import { OrientationVector } from '@viamrobotics/three'
import * as THREE from 'three';
import { OrientationVector } from '@viamrobotics/three';

const ov = new OrientationVector()
const quat = new THREE.Quaternion()
const euler = new THREE.Euler()
const ov = new OrientationVector();
const quat = new THREE.Quaternion();
const euler = new THREE.Euler();

// Common conversions:
ov.toQuaternion(quat)
ov.toEuler(euler)
ov.toQuaternion(quat);
ov.toEuler(euler);

ov.setFromQuaternion(quat)
ov.setFromQuaternion(quat);
```

### ViamObject3D

Extends THREE.Object3D and adds an `.orientationVector` that auto-updates when other rotation formats are updated.

```ts
import { ViamObject3D } from '@viamrobotics/three'
```
import { ViamObject3D } from '@viamrobotics/three';
```
33 changes: 17 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@viamrobotics/three",
"version": "0.0.4",
"version": "0.0.5",
"license": "Apache-2.0",
"type": "module",
"files": [
Expand Down Expand Up @@ -39,26 +39,27 @@
],
"devDependencies": {
"@0b5vr/tweakpane-plugin-rotation": "^0.2.0",
"@tweakpane/core": "^2.0.1",
"@tweakpane/plugin-essentials": "^0.2.0",
"@types/three": "^0.159.0",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@viamrobotics/eslint-config": "^0.3.0",
"@tweakpane/core": "^2.0.3",
"@tweakpane/plugin-essentials": "^0.2.1",
"@types/three": "^0.162.0",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"@viamrobotics/eslint-config": "^0.4.0",
"@viamrobotics/prettier-config": "^0.3.4",
"@viamrobotics/typescript-config": "^0.1.0",
"eslint": "^8.55.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-sonarjs": "^0.23.0",
"eslint-plugin-unicorn": "^49.0.0",
"prettier": "^3.1.1",
"three": "^0.159.0",
"eslint-plugin-sonarjs": "^0.24.0",
"eslint-plugin-unicorn": "^51.0.1",
"eslint-plugin-vitest": "^0.3.22",
"prettier": "^3.2.5",
"three": "^0.162.0",
"three-inspect": "^0.3.4",
"trzy": "^0.3.17",
"tweakpane": "^4.0.1",
"typescript": "^5.3.2",
"vite": "^5.0.5",
"vitest": "^0.34.6"
"tweakpane": "^4.0.3",
"typescript": "^5.3.3",
"vite": "^5.1.4",
"vitest": "^1.3.1"
},
"peerDependencies": {
"three": "*"
Expand Down
247 changes: 121 additions & 126 deletions playground/conversions.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,43 @@
import * as THREE from 'three'
import { Units, type InputTypes } from './types'
import { rotations } from './rotations'
import { OrientationVector, ViamObject3D } from '../src/main'

// These globals are just added to allow playing around in the console as well.
window.THREE = THREE

// @ts-expect-error For debugging
window.OrientationVector = OrientationVector

// @ts-expect-error For debugging
window.ViamObject3D = ViamObject3D

const v3 = new THREE.Vector3()
const quat = new THREE.Quaternion()
const euler = new THREE.Euler()
const m4 = new THREE.Matrix4()
const ov = new OrientationVector()
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable id-length */
/* eslint-disable unicorn/prefer-switch */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
import * as THREE from 'three';
import { Units, type InputTypes } from './types';
import { rotations } from './rotations';
import { OrientationVector } from '../src/main';

const v3 = new THREE.Vector3();
const quat = new THREE.Quaternion();
const euler = new THREE.Euler();
const m4 = new THREE.Matrix4();
const ov = new OrientationVector();

const toRad = (x: number, unit: Units): number => {
if (unit === Units.degrees) {
return x / 180 * Math.PI
} else {
return x
}
}
return unit === Units.degrees ? (x / 180) * Math.PI : x;
};

const toAngle = (x: number, unit: Units): number => {
if (unit === Units.degrees) {
return x * 180 / Math.PI
} else {
return x
}
}
return unit === Units.degrees ? (x * 180) / Math.PI : x;
};

const toReal = (x: number): number => {
if (!Number.isNaN(x) && Number.isFinite(x)) {
return Number.parseFloat(x.toFixed(7))
} else {
return x
}
}
return !Number.isNaN(x) && Number.isFinite(x)
? Number.parseFloat(x.toFixed(7))
: x;
};

const toFixedWidth = (x: number): number => {
if (!Number.isNaN(x) && Number.isFinite(x)) {
let s = x.toFixed(7)
if (x >= 0) s = ' ' + s
return Number.parseFloat(s)
} else {
return x
}
}
if (!Number.isNaN(x) && Number.isFinite(x)) {
let s = x.toFixed(7);
if (x >= 0) {
s = ` ${s}`;
}
return Number.parseFloat(s);
}

return x;
};

export const conversion = (type: InputTypes, units: Units) => {
if (type === 'ov') {
Expand All @@ -59,117 +46,125 @@ export const conversion = (type: InputTypes, units: Units) => {
rotations.ov.y,
rotations.ov.z,
toRad(rotations.ov.w, units)
)

quat.copy(ov.toQuaternion(quat))
);

quat.copy(ov.toQuaternion(quat));
} else if (type === 'matrix') {
const { row1, row2, row3 } = rotations.matrix
const { row1, row2, row3 } = rotations.matrix;

m4.set(
row1.x, row1.y, row1.z, 1,
row2.x, row2.y, row2.z, 1,
row3.x, row3.y, row3.z, 1,
0, 0, 0, 1
)

quat.setFromRotationMatrix(m4)

row1.x,
row1.y,
row1.z,
1,
row2.x,
row2.y,
row2.z,
1,
row3.x,
row3.y,
row3.z,
1,
0,
0,
0,
1
);

quat.setFromRotationMatrix(m4);
} else if (type === 'quaternion') {
quat.set(
rotations.quaternion.x,
rotations.quaternion.y,
rotations.quaternion.z,
rotations.quaternion.w
)

);
} else if (type === 'axis angle') {
const aa = rotations.axisAngle
const axis = v3
axis.set(aa.xyz.x, aa.xyz.y, aa.xyz.z)
axis.normalize()

quat.setFromAxisAngle(axis, toRad(aa.angle, units))
const aa = rotations.axisAngle;
const axis = v3;
axis.set(aa.xyz.x, aa.xyz.y, aa.xyz.z);
axis.normalize();

quat.setFromAxisAngle(axis, toRad(aa.angle, units));
} else if (type === 'axis with angle magnitude') {
const aa = rotations.axisAngleMagnitude
const axis = v3
axis.set(aa.x, aa.y, aa.z)

const angle = toRad(axis.length(), units)
axis.normalize()
const aa = rotations.axisAngleMagnitude;
const axis = v3;
axis.set(aa.x, aa.y, aa.z);

quat.setFromAxisAngle(axis, angle)
const angle = toRad(axis.length(), units);
axis.normalize();

quat.setFromAxisAngle(axis, angle);
} else if (type === 'euler') {
const eu = rotations.euler

quat.setFromEuler(euler.set(
toRad(eu.xyz.x, units),
toRad(eu.xyz.y, units),
toRad(eu.xyz.z, units),
eu.order as THREE.EulerOrder
))

const eu = rotations.euler;

quat.setFromEuler(
euler.set(
toRad(eu.xyz.x, units),
toRad(eu.xyz.y, units),
toRad(eu.xyz.z, units),
eu.order as THREE.EulerOrder
)
);
}

quat.normalize()
quat.normalize();

rotations.quaternion.x = toReal(quat.x)
rotations.quaternion.y = toReal(quat.y)
rotations.quaternion.z = toReal(quat.z)
rotations.quaternion.w = toReal(quat.w)
rotations.quaternion.x = toReal(quat.x);
rotations.quaternion.y = toReal(quat.y);
rotations.quaternion.z = toReal(quat.z);
rotations.quaternion.w = toReal(quat.w);

ov.setFromQuaternion(quat)
rotations.ov.w = toAngle(ov.th, units)
rotations.ov.x = ov.x
rotations.ov.y = ov.y
rotations.ov.z = ov.z
ov.setFromQuaternion(quat);
rotations.ov.w = toAngle(ov.th, units);
rotations.ov.x = ov.x;
rotations.ov.y = ov.y;
rotations.ov.z = ov.z;

m4.makeRotationFromQuaternion(quat)
m4.makeRotationFromQuaternion(quat);

{
const r = m4.elements
rotations.matrix.row1.x = toFixedWidth(r[0]!)
rotations.matrix.row1.y = toFixedWidth(r[4]!)
rotations.matrix.row1.z = toFixedWidth(r[8]!)
rotations.matrix.row2.x = toFixedWidth(r[1]!)
rotations.matrix.row2.y = toFixedWidth(r[5]!)
rotations.matrix.row2.z = toFixedWidth(r[9]!)
rotations.matrix.row3.x = toFixedWidth(r[2]!)
rotations.matrix.row3.y = toFixedWidth(r[6]!)
rotations.matrix.row3.z = toFixedWidth(r[10]!)
const r = m4.elements;
rotations.matrix.row1.x = toFixedWidth(r[0]!);
rotations.matrix.row1.y = toFixedWidth(r[4]!);
rotations.matrix.row1.z = toFixedWidth(r[8]!);
rotations.matrix.row2.x = toFixedWidth(r[1]!);
rotations.matrix.row2.y = toFixedWidth(r[5]!);
rotations.matrix.row2.z = toFixedWidth(r[9]!);
rotations.matrix.row3.x = toFixedWidth(r[2]!);
rotations.matrix.row3.y = toFixedWidth(r[6]!);
rotations.matrix.row3.z = toFixedWidth(r[10]!);
}

const axis = [0, 0, 0]
const angle = 2 * Math.acos(quat.w)
const axis = [0, 0, 0];
const angle = 2 * Math.acos(quat.w);

if (1 - (quat.w * quat.w) < 0.000001) {
axis[0] = quat.x
axis[1] = quat.y
axis[2] = quat.z
if (1 - quat.w * quat.w < 0.000_001) {
axis[0] = quat.x;
axis[1] = quat.y;
axis[2] = quat.z;
} else {
// http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/
const s = Math.sqrt(1 - (quat.w * quat.w))
axis[0] = quat.x / s
axis[1] = quat.y / s
axis[2] = quat.z / s
const s = Math.sqrt(1 - quat.w * quat.w);
axis[0] = quat.x / s;
axis[1] = quat.y / s;
axis[2] = quat.z / s;
}

rotations.axisAngle.xyz.x = toReal(axis[0])
rotations.axisAngle.xyz.y = toReal(axis[1])
rotations.axisAngle.xyz.z = toReal(axis[2])
rotations.axisAngle.angle = toReal(toAngle(angle, units))
rotations.axisAngle.xyz.x = toReal(axis[0]);
rotations.axisAngle.xyz.y = toReal(axis[1]);
rotations.axisAngle.xyz.z = toReal(axis[2]);
rotations.axisAngle.angle = toReal(toAngle(angle, units));

rotations.axisAngleMagnitude.x = toReal(toAngle(axis[0] * angle, units));
rotations.axisAngleMagnitude.y = toReal(toAngle(axis[1] * angle, units));
rotations.axisAngleMagnitude.z = toReal(toAngle(axis[2] * angle, units));

rotations.axisAngleMagnitude.x = toReal(toAngle(axis[0] * angle, units))
rotations.axisAngleMagnitude.y = toReal(toAngle(axis[1] * angle, units))
rotations.axisAngleMagnitude.z = toReal(toAngle(axis[2] * angle, units))

{
euler.setFromRotationMatrix(m4, rotations.euler.order as THREE.EulerOrder)
const [x, y, z] = euler.toArray() as [number, number, number]
rotations.euler.xyz.x = toReal(toAngle(x, units))
rotations.euler.xyz.y = toReal(toAngle(y, units))
rotations.euler.xyz.z = toReal(toAngle(z, units))
euler.setFromRotationMatrix(m4, rotations.euler.order as THREE.EulerOrder);
const [x, y, z] = euler.toArray() as [number, number, number];
rotations.euler.xyz.x = toReal(toAngle(x, units));
rotations.euler.xyz.y = toReal(toAngle(y, units));
rotations.euler.xyz.z = toReal(toAngle(z, units));
}
}
};
Loading
Loading