Quellcode durchsuchen

refactored principal-axes calculation

Alexander Rose vor 5 Jahren
Ursprung
Commit
a725e577be

+ 62 - 65
src/mol-math/linear-algebra/matrix/principal-axes.ts

@@ -5,36 +5,31 @@
  */
  */
 
 
 import Matrix from './matrix';
 import Matrix from './matrix';
-import { Vec3, Mat4 } from '../3d';
+import { Vec3 } from '../3d';
 import { svd } from './svd';
 import { svd } from './svd';
 import { NumberArray } from '../../../mol-util/type-helpers';
 import { NumberArray } from '../../../mol-util/type-helpers';
+import { Axes3D } from '../../geometry';
 
 
 export { PrincipalAxes }
 export { PrincipalAxes }
 
 
 interface PrincipalAxes {
 interface PrincipalAxes {
-    begA: Vec3
-    endA: Vec3
-    begB: Vec3
-    endB: Vec3
-    begC: Vec3
-    endC: Vec3
-
-    center: Vec3
-
-    vecA: Vec3
-    vecB: Vec3
-    vecC: Vec3
-
-    normVecA: Vec3
-    normVecB: Vec3
-    normVecC: Vec3
+    momentsAxes: Axes3D
+    boxAxes: Axes3D
 }
 }
 
 
 namespace PrincipalAxes {
 namespace PrincipalAxes {
-    /**
-     * @param points 3xN matrix
-     */
-    export function ofPoints(points: Matrix<3, number>): PrincipalAxes {
+    export function ofPositions(positions: NumberArray): PrincipalAxes {
+        const momentsAxes = calculateMomentsAxes(positions)
+        const boxAxes = calculateBoxAxes(positions, momentsAxes)
+        return { momentsAxes, boxAxes }
+    }
+
+    export function calculateMomentsAxes(positions: NumberArray): Axes3D {
+        if (positions.length === 3) {
+            return Axes3D.create(Vec3.fromArray(Vec3(), positions, 0), Vec3(), Vec3(), Vec3())
+        }
+
+        const points = Matrix.fromArray(positions, 3, positions.length / 3)
         const n = points.rows
         const n = points.rows
         const n3 = n / 3
         const n3 = n / 3
         const A = Matrix.create(3, 3)
         const A = Matrix.create(3, 3)
@@ -49,49 +44,33 @@ namespace PrincipalAxes {
         Matrix.multiplyABt(A, pointsT, pointsT)
         Matrix.multiplyABt(A, pointsT, pointsT)
         svd(A, W, U, V)
         svd(A, W, U, V)
 
 
-        // center
-        const center = Vec3.create(mean[0], mean[1], mean[2])
-
-        // normalized
-        const normVecA = Vec3.create(U.data[0], U.data[3], U.data[6])
-        const normVecB = Vec3.create(U.data[1], U.data[4], U.data[7])
-        const normVecC = Vec3.create(U.data[2], U.data[5], U.data[8])
-
-        // scaled
-        const vecA = Vec3.scale(Vec3(), normVecA, Math.sqrt(W.data[0] / n3))
-        const vecB = Vec3.scale(Vec3(), normVecB, Math.sqrt(W.data[1] / n3))
-        const vecC = Vec3.scale(Vec3(), normVecC, Math.sqrt(W.data[2] / n3))
-
-        // points
-        const begA = Vec3.sub(Vec3.clone(center), center, vecA)
-        const endA = Vec3.add(Vec3.clone(center), center, vecA)
-        const begB = Vec3.sub(Vec3.clone(center), center, vecB)
-        const endB = Vec3.add(Vec3.clone(center), center, vecB)
-        const begC = Vec3.sub(Vec3.clone(center), center, vecC)
-        const endC = Vec3.add(Vec3.clone(center), center, vecC)
-
-        return {
-            begA, endA, begB, endB, begC, endC,
-            center,
-            vecA, vecB, vecC,
-            normVecA, normVecB, normVecC
-        }
-    }
+        // origin
+        const origin = Vec3.create(mean[0], mean[1], mean[2])
 
 
-    /**
-     * Set basis matrix for given axes
-     */
-    export function setBasisMatrix(out: Mat4, principalAxes: PrincipalAxes) {
-        Mat4.setAxes(out, principalAxes.normVecB, principalAxes.normVecA, principalAxes.normVecC)
-        if (Mat4.determinant(out) < 0) Mat4.scaleUniformly(out, out, -1)
-        return out
+        // directions
+        const dirA = Vec3.create(U.data[0], U.data[3], U.data[6])
+        const dirB = Vec3.create(U.data[1], U.data[4], U.data[7])
+        const dirC = Vec3.create(U.data[2], U.data[5], U.data[8])
+        Vec3.scale(dirA, dirA, Math.sqrt(W.data[0] / n3))
+        Vec3.scale(dirB, dirB, Math.sqrt(W.data[1] / n3))
+        Vec3.scale(dirC, dirC, Math.sqrt(W.data[2] / n3))
+
+        return Axes3D.create(origin, dirA, dirB, dirC)
     }
     }
 
 
+    const tmpBoxVec = Vec3()
+    const tmpBoxVecA = Vec3()
+    const tmpBoxVecB = Vec3()
+    const tmpBoxVecC = Vec3()
     /**
     /**
      * Get the scale/length for each dimension for a box around the axes
      * Get the scale/length for each dimension for a box around the axes
      * to enclose the given positions
      * to enclose the given positions
      */
      */
-    export function getProjectedScale(positions: NumberArray, principalAxes: PrincipalAxes) {
+    export function calculateBoxAxes(positions: NumberArray, momentsAxes: Axes3D): Axes3D {
+        if (positions.length === 3) {
+            return Axes3D.clone(momentsAxes)
+        }
+
         let d1a = -Infinity
         let d1a = -Infinity
         let d1b = -Infinity
         let d1b = -Infinity
         let d2a = -Infinity
         let d2a = -Infinity
@@ -102,7 +81,10 @@ namespace PrincipalAxes {
         const p = Vec3()
         const p = Vec3()
         const t = Vec3()
         const t = Vec3()
 
 
-        const { center, normVecA, normVecB, normVecC } = principalAxes
+        const center = momentsAxes.origin
+        const normVecA = Vec3.normalize(tmpBoxVecA, momentsAxes.dirA)
+        const normVecB = Vec3.normalize(tmpBoxVecB, momentsAxes.dirB)
+        const normVecC = Vec3.normalize(tmpBoxVecC, momentsAxes.dirC)
 
 
         for (let i = 0, il = positions.length; i < il; i += 3) {
         for (let i = 0, il = positions.length; i < il; i += 3) {
             Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), normVecA, center)
             Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), normVecA, center)
@@ -133,13 +115,28 @@ namespace PrincipalAxes {
             }
             }
         }
         }
 
 
-        return {
-            d1a: d1a,
-            d2a: d2a,
-            d3a: d3a,
-            d1b: -d1b,
-            d2b: -d2b,
-            d3b: -d3b
+        const dirA = Vec3.setMagnitude(Vec3(), normVecA, (d1a + d1b) / 2)
+        const dirB = Vec3.setMagnitude(Vec3(), normVecB, (d2a + d2b) / 2)
+        const dirC = Vec3.setMagnitude(Vec3(), normVecC, (d3a + d3b) / 2)
+
+        const origin = Vec3()
+        const addCornerHelper = function (d1: number, d2: number, d3: number) {
+            Vec3.copy(tmpBoxVec, center)
+            Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, normVecA, d1)
+            Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, normVecB, d2)
+            Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, normVecC, d3)
+            Vec3.add(origin, origin, tmpBoxVec)
         }
         }
+        addCornerHelper(d1a, d2a, d3a)
+        addCornerHelper(d1a, d2a, -d3b)
+        addCornerHelper(d1a, -d2b, -d3b)
+        addCornerHelper(d1a, -d2b, d3a)
+        addCornerHelper(-d1b, -d2b, -d3b)
+        addCornerHelper(-d1b, -d2b, d3a)
+        addCornerHelper(-d1b, d2a, d3a)
+        addCornerHelper(-d1b, d2a, -d3b)
+        Vec3.scale(origin, origin, 1 / 8)
+
+        return Axes3D.create(origin, dirA, dirB, dirC)
     }
     }
 }
 }

+ 4 - 4
src/mol-model/structure/structure/carbohydrates/compute.ts

@@ -13,7 +13,7 @@ import { PrincipalAxes } from '../../../../mol-math/linear-algebra/matrix/princi
 import { fillSerial } from '../../../../mol-util/array';
 import { fillSerial } from '../../../../mol-util/array';
 import { ResidueIndex, Model } from '../../model';
 import { ResidueIndex, Model } from '../../model';
 import { ElementSymbol } from '../../model/types';
 import { ElementSymbol } from '../../model/types';
-import { getPositionMatrix } from '../../util';
+import { getPositions } from '../../util';
 import StructureElement from '../element';
 import StructureElement from '../element';
 import Structure from '../structure';
 import Structure from '../structure';
 import Unit from '../unit';
 import Unit from '../unit';
@@ -195,9 +195,9 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
                     const ringAtoms = rings.all[sugarRings[j]];
                     const ringAtoms = rings.all[sugarRings[j]];
                     const anomericCarbon = getAnomericCarbon(unit, ringAtoms)
                     const anomericCarbon = getAnomericCarbon(unit, ringAtoms)
 
 
-                    const pa = PrincipalAxes.ofPoints(getPositionMatrix(unit, ringAtoms))
-                    const center = Vec3.copy(Vec3.zero(), pa.center)
-                    const normal = Vec3.copy(Vec3.zero(), pa.normVecC)
+                    const ma = PrincipalAxes.calculateMomentsAxes(getPositions(unit, ringAtoms))
+                    const center = Vec3.copy(Vec3.zero(), ma.origin)
+                    const normal = Vec3.copy(Vec3.zero(), ma.dirC)
                     const direction = getDirection(Vec3.zero(), unit, anomericCarbon, center)
                     const direction = getDirection(Vec3.zero(), unit, anomericCarbon, center)
                     Vec3.orthogonalize(direction, normal, direction)
                     Vec3.orthogonalize(direction, normal, direction)
 
 

+ 1 - 2
src/mol-model/structure/structure/element/loci.ts

@@ -20,7 +20,6 @@ import { UnitIndex } from './element';
 import { Location } from './location';
 import { Location } from './location';
 import { ChainIndex } from '../../model/indexing';
 import { ChainIndex } from '../../model/indexing';
 import { PrincipalAxes } from '../../../../mol-math/linear-algebra/matrix/principal-axes';
 import { PrincipalAxes } from '../../../../mol-math/linear-algebra/matrix/principal-axes';
-import Matrix from '../../../../mol-math/linear-algebra/matrix/matrix';
 import { NumberArray } from '../../../../mol-util/type-helpers';
 import { NumberArray } from '../../../../mol-util/type-helpers';
 
 
 /** Represents multiple structure element index locations */
 /** Represents multiple structure element index locations */
@@ -430,7 +429,7 @@ export namespace Loci {
     export function getPrincipalAxes(loci: Loci): PrincipalAxes {
     export function getPrincipalAxes(loci: Loci): PrincipalAxes {
         const elementCount = size(loci)
         const elementCount = size(loci)
         const positions = toPositionsArray(loci, new Float32Array(3 * elementCount))
         const positions = toPositionsArray(loci, new Float32Array(3 * elementCount))
-        return PrincipalAxes.ofPoints(Matrix.fromArray(positions, 3, elementCount))
+        return PrincipalAxes.ofPositions(positions)
     }
     }
 
 
     function sourceIndex(unit: Unit, element: ElementIndex) {
     function sourceIndex(unit: Unit, element: ElementIndex) {

+ 7 - 7
src/mol-model/structure/util.ts

@@ -8,7 +8,7 @@ import { Model, ResidueIndex, ElementIndex } from './model';
 import { MoleculeType, AtomRole, PolymerTypeAtomRoleId, getMoleculeType, PolymerType } from './model/types';
 import { MoleculeType, AtomRole, PolymerTypeAtomRoleId, getMoleculeType, PolymerType } from './model/types';
 import { Vec3 } from '../../mol-math/linear-algebra';
 import { Vec3 } from '../../mol-math/linear-algebra';
 import { Unit } from './structure';
 import { Unit } from './structure';
-import Matrix from '../../mol-math/linear-algebra/matrix/matrix';
+import { NumberArray } from '../../mol-util/type-helpers';
 
 
 export function getCoarseBegCompId(unit: Unit.Spheres | Unit.Gaussians, element: ElementIndex) {
 export function getCoarseBegCompId(unit: Unit.Spheres | Unit.Gaussians, element: ElementIndex) {
     const entityKey = unit.coarseElements.entityKey[element]
     const entityKey = unit.coarseElements.entityKey[element]
@@ -80,14 +80,14 @@ export function elementLabel(model: Model, index: ElementIndex) {
 //     return Vec3.distance(centerMin, centroid)
 //     return Vec3.distance(centerMin, centroid)
 // }
 // }
 
 
-const matrixPos = Vec3.zero()
-export function getPositionMatrix(unit: Unit, indices: ArrayLike<number>): Matrix<3, number> {
+const tmpPositionsVec = Vec3.zero()
+export function getPositions(unit: Unit, indices: ArrayLike<number>): NumberArray {
     const pos = unit.conformation.position
     const pos = unit.conformation.position
-    const mat = Matrix.create(3, indices.length)
+    const positions = new Float32Array(indices.length * 3)
     const { elements } = unit
     const { elements } = unit
     for (let i = 0, il = indices.length; i < il; ++i) {
     for (let i = 0, il = indices.length; i < il; ++i) {
-        pos(elements[indices[i]], matrixPos)
-        Vec3.toArray(matrixPos, mat.data, i * 3)
+        pos(elements[indices[i]], tmpPositionsVec)
+        Vec3.toArray(tmpPositionsVec, positions, i * 3)
     }
     }
-    return mat
+    return positions
 }
 }

+ 8 - 9
src/mol-plugin/ui/structure/selection.tsx

@@ -14,6 +14,8 @@ import { Interactivity } from '../../util/interactivity';
 import { ParameterControls } from '../controls/parameters';
 import { ParameterControls } from '../controls/parameters';
 import { stripTags } from '../../../mol-util/string';
 import { stripTags } from '../../../mol-util/string';
 import { StructureElement } from '../../../mol-model/structure';
 import { StructureElement } from '../../../mol-model/structure';
+import { Vec3 } from '../../../mol-math/linear-algebra';
+import { Sphere3D } from '../../../mol-math/geometry';
 
 
 const SSQ = StructureSelectionQueries
 const SSQ = StructureSelectionQueries
 const DefaultQueries: (keyof typeof SSQ)[] = [
 const DefaultQueries: (keyof typeof SSQ)[] = [
@@ -58,21 +60,18 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
     focus = () => {
     focus = () => {
         const { extraRadius, minRadius, durationMs } = this.state
         const { extraRadius, minRadius, durationMs } = this.state
         if (this.plugin.helpers.structureSelectionManager.stats.elementCount === 0) return
         if (this.plugin.helpers.structureSelectionManager.stats.elementCount === 0) return
-        const { sphere } = this.plugin.helpers.structureSelectionManager.getBoundary();
-        const radius = Math.max(sphere.radius + extraRadius, minRadius);
         const principalAxes = this.plugin.helpers.structureSelectionManager.getPrincipalAxes();
         const principalAxes = this.plugin.helpers.structureSelectionManager.getPrincipalAxes();
-        const { center, normVecA, normVecC } = principalAxes
-        this.plugin.canvas3d.camera.focus(center, radius, durationMs, normVecA, normVecC);
+        const { origin, dirA, dirC } = principalAxes.boxAxes
+        const radius = Math.max(Vec3.magnitude(dirA) + extraRadius, minRadius);
+        this.plugin.canvas3d.camera.focus(origin, radius, durationMs, dirA, dirC);
     }
     }
 
 
-    focusSingle(loci: StructureElement.Loci) {
+    focusLoci(loci: StructureElement.Loci) {
         return () => {
         return () => {
             const { extraRadius, minRadius, durationMs } = this.state
             const { extraRadius, minRadius, durationMs } = this.state
             if (this.plugin.helpers.structureSelectionManager.stats.elementCount === 0) return
             if (this.plugin.helpers.structureSelectionManager.stats.elementCount === 0) return
-            const { sphere } = StructureElement.Loci.getBoundary(loci);
+            const sphere = Sphere3D.fromAxes3D(Sphere3D(), StructureElement.Loci.getPrincipalAxes(loci).boxAxes)
             const radius = Math.max(sphere.radius + extraRadius, minRadius);
             const radius = Math.max(sphere.radius + extraRadius, minRadius);
-            // const principalAxes = this.plugin.helpers.structureSelectionManager.getPrincipalAxes();
-            // const { center, normVecA, normVecC } = principalAxes
             this.plugin.canvas3d.camera.focus(sphere.center, radius, durationMs);
             this.plugin.canvas3d.camera.focus(sphere.center, radius, durationMs);
         }
         }
     }
     }
@@ -151,7 +150,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
             const e = mng.latestLoci[i];
             const e = mng.latestLoci[i];
             latest.push(<li key={e!.label}>
             latest.push(<li key={e!.label}>
                 <button className='msp-btn msp-btn-block msp-form-control' style={{ borderRight: '6px solid transparent', overflow: 'hidden' }}
                 <button className='msp-btn msp-btn-block msp-form-control' style={{ borderRight: '6px solid transparent', overflow: 'hidden' }}
-                    title='Click to focus.' onClick={this.focusSingle(e.loci)}>
+                    title='Click to focus.' onClick={this.focusLoci(e.loci)}>
                     <span dangerouslySetInnerHTML={{ __html: e.label }} />
                     <span dangerouslySetInnerHTML={{ __html: e.label }} />
                 </button>
                 </button>
                 {/* <div>
                 {/* <div>

+ 1 - 2
src/mol-plugin/util/structure-element-selection.ts

@@ -16,7 +16,6 @@ import { Vec3 } from '../../mol-math/linear-algebra';
 import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
 import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper';
 import { Boundary } from '../../mol-model/structure/structure/util/boundary';
 import { Boundary } from '../../mol-model/structure/structure/util/boundary';
 import { PrincipalAxes } from '../../mol-math/linear-algebra/matrix/principal-axes';
 import { PrincipalAxes } from '../../mol-math/linear-algebra/matrix/principal-axes';
-import Matrix from '../../mol-math/linear-algebra/matrix/matrix';
 import { arrayRemoveAtInPlace } from '../../mol-util/array';
 import { arrayRemoveAtInPlace } from '../../mol-util/array';
 
 
 const boundaryHelper = new BoundaryHelper();
 const boundaryHelper = new BoundaryHelper();
@@ -92,7 +91,7 @@ class StructureElementSelectionManager {
             StructureElement.Loci.toPositionsArray(v.selection, positions, offset)
             StructureElement.Loci.toPositionsArray(v.selection, positions, offset)
             offset += StructureElement.Loci.size(v.selection) * 3
             offset += StructureElement.Loci.size(v.selection) * 3
         })
         })
-        return PrincipalAxes.ofPoints(Matrix.fromArray(positions, 3, elementCount))
+        return PrincipalAxes.ofPositions(positions)
     }
     }
 
 
     get stats() {
     get stats() {

+ 45 - 42
src/mol-plugin/util/structure-orientation.ts

@@ -13,11 +13,11 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
 import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
 import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
 import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
 import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
 import { Vec3, Mat4 } from '../../mol-math/linear-algebra';
-import Matrix from '../../mol-math/linear-algebra/matrix/matrix';
 import { PrincipalAxes } from '../../mol-math/linear-algebra/matrix/principal-axes';
 import { PrincipalAxes } from '../../mol-math/linear-algebra/matrix/principal-axes';
 import { createCage } from '../../mol-geo/primitive/cage';
 import { createCage } from '../../mol-geo/primitive/cage';
 import { stringToWords } from '../../mol-util/string';
 import { stringToWords } from '../../mol-util/string';
 import { structureElementStatsLabel } from '../../mol-theme/label';
 import { structureElementStatsLabel } from '../../mol-theme/label';
+import { Axes3D } from '../../mol-math/geometry';
 
 
 const tmpMatrixPos = Vec3.zero()
 const tmpMatrixPos = Vec3.zero()
 function getPositions(structure: Structure) {
 function getPositions(structure: Structure) {
@@ -38,7 +38,6 @@ function getPositions(structure: Structure) {
 interface OrientationData {
 interface OrientationData {
     label: string
     label: string
     principalAxes: PrincipalAxes
     principalAxes: PrincipalAxes
-    projectedScale: { d1a: number, d2a: number, d3a: number, d1b: number, d2b: number, d3b: number }
 }
 }
 
 
 const OrientationVisuals = { 'principal-axes': '', 'oriented-box': '' }
 const OrientationVisuals = { 'principal-axes': '', 'oriented-box': '' }
@@ -48,8 +47,8 @@ const OrientationVisualOptions = Object.keys(OrientationVisuals).map(name => [na
 export const OrientationParams = {
 export const OrientationParams = {
     ...Mesh.Params,
     ...Mesh.Params,
     visuals: PD.MultiSelect<OrientationVisualName>(['oriented-box'], OrientationVisualOptions),
     visuals: PD.MultiSelect<OrientationVisualName>(['oriented-box'], OrientationVisualOptions),
-    orientationColor: PD.Color(ColorNames.orange),
-    orientationScale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 })
+    color: PD.Color(ColorNames.orange),
+    scale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 })
 }
 }
 export type OrientationParams = typeof OrientationParams
 export type OrientationParams = typeof OrientationParams
 export type OrientationProps = PD.Values<OrientationParams>
 export type OrientationProps = PD.Values<OrientationParams>
@@ -59,33 +58,38 @@ enum VisualGroup {
     OrientedBox = 2
     OrientedBox = 2
 }
 }
 
 
-function getVolume(data: OrientationData) {
-    const { d1a, d2a, d3a, d1b, d2b, d3b } = data.projectedScale
-    return (d1a - d1b) * (d2a - d2b) * (d3a - d3b)
-}
+const tmpAxesVec = Vec3()
+function buildMomentsAxes(state: MeshBuilder.State, data: OrientationData, props: OrientationProps) {
+    const { origin, dirA, dirB, dirC } = data.principalAxes.momentsAxes
 
 
-function buildPrincipalAxes(state: MeshBuilder.State, data: OrientationData, props: OrientationProps) {
     const vertices = new Float32Array(6 * 3)
     const vertices = new Float32Array(6 * 3)
     const edges = new Uint8Array([0, 1, 2, 3, 4, 5])
     const edges = new Uint8Array([0, 1, 2, 3, 4, 5])
-    Vec3.toArray(data.principalAxes.begA, vertices, 0)
-    Vec3.toArray(data.principalAxes.endA, vertices, 3)
-    Vec3.toArray(data.principalAxes.begB, vertices, 6)
-    Vec3.toArray(data.principalAxes.endB, vertices, 9)
-    Vec3.toArray(data.principalAxes.begC, vertices, 12)
-    Vec3.toArray(data.principalAxes.endC, vertices, 15)
+    Vec3.add(tmpAxesVec, origin, dirA)
+    Vec3.toArray(Vec3.add(tmpAxesVec, origin, dirA), vertices, 0)
+    Vec3.toArray(Vec3.sub(tmpAxesVec, origin, dirA), vertices, 3)
+    Vec3.toArray(Vec3.add(tmpAxesVec, origin, dirB), vertices, 6)
+    Vec3.toArray(Vec3.sub(tmpAxesVec, origin, dirB), vertices, 9)
+    Vec3.toArray(Vec3.add(tmpAxesVec, origin, dirC), vertices, 12)
+    Vec3.toArray(Vec3.sub(tmpAxesVec, origin, dirC), vertices, 15)
 
 
-    const matrix = Mat4.fromTranslation(Mat4(), Vec3.inverse(Vec3(), data.principalAxes.center))
+    const matrix = Mat4.fromTranslation(Mat4(), Vec3.inverse(Vec3(), origin))
 
 
     const cage = createCage(vertices, edges)
     const cage = createCage(vertices, edges)
-    const radius = (Math.cbrt(getVolume(data)) / 300) * props.orientationScale
+    const volume = Axes3D.volume(data.principalAxes.boxAxes)
+    const radius = (Math.cbrt(volume) / 300) * props.scale
     state.currentGroup = VisualGroup.PrincipalAxes
     state.currentGroup = VisualGroup.PrincipalAxes
     MeshBuilder.addCage(state, matrix, cage, radius, 2, 20)
     MeshBuilder.addCage(state, matrix, cage, radius, 2, 20)
 }
 }
 
 
-const tmpBoxVec = Vec3()
+const tmpBoxVecCorner = Vec3()
+const tmpBoxVecA = Vec3()
+const tmpBoxVecB = Vec3()
+const tmpBoxVecC = Vec3()
 function buildOrientedBox(state: MeshBuilder.State, data: OrientationData, props: OrientationProps) {
 function buildOrientedBox(state: MeshBuilder.State, data: OrientationData, props: OrientationProps) {
-    const { center, normVecA, normVecB, normVecC } = data.principalAxes
-    const { d1a, d2a, d3a, d1b, d2b, d3b } = data.projectedScale
+    const { origin, dirA, dirB, dirC } = data.principalAxes.boxAxes
+    const negDirA = Vec3.negate(tmpBoxVecA, dirA)
+    const negDirB = Vec3.negate(tmpBoxVecB, dirB)
+    const negDirC = Vec3.negate(tmpBoxVecC, dirC)
 
 
     const vertices = new Float32Array(8 * 3)
     const vertices = new Float32Array(8 * 3)
     const edges = new Uint8Array([
     const edges = new Uint8Array([
@@ -94,27 +98,28 @@ function buildOrientedBox(state: MeshBuilder.State, data: OrientationData, props
     ])
     ])
 
 
     let offset = 0
     let offset = 0
-    const addCornerHelper = function (d1: number, d2: number, d3: number) {
-        Vec3.copy(tmpBoxVec, center)
-        Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, normVecA, d1)
-        Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, normVecB, d2)
-        Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, normVecC, d3)
-        Vec3.toArray(tmpBoxVec, vertices, offset)
+    const addCornerHelper = function (v1: Vec3, v2: Vec3, v3: Vec3) {
+        Vec3.copy(tmpBoxVecCorner, origin)
+        Vec3.add(tmpBoxVecCorner, tmpBoxVecCorner, v1)
+        Vec3.add(tmpBoxVecCorner, tmpBoxVecCorner, v2)
+        Vec3.add(tmpBoxVecCorner, tmpBoxVecCorner, v3)
+        Vec3.toArray(tmpBoxVecCorner, vertices, offset)
         offset += 3
         offset += 3
     }
     }
-    addCornerHelper(d1a, d2a, d3a)
-    addCornerHelper(d1a, d2a, d3b)
-    addCornerHelper(d1a, d2b, d3b)
-    addCornerHelper(d1a, d2b, d3a)
-    addCornerHelper(d1b, d2b, d3b)
-    addCornerHelper(d1b, d2b, d3a)
-    addCornerHelper(d1b, d2a, d3a)
-    addCornerHelper(d1b, d2a, d3b)
+    addCornerHelper(dirA, dirB, dirC)
+    addCornerHelper(dirA, dirB, negDirC)
+    addCornerHelper(dirA, negDirB, negDirC)
+    addCornerHelper(dirA, negDirB, dirC)
+    addCornerHelper(negDirA, negDirB, negDirC)
+    addCornerHelper(negDirA, negDirB, dirC)
+    addCornerHelper(negDirA, dirB, dirC)
+    addCornerHelper(negDirA, dirB, negDirC)
 
 
-    const matrix = Mat4.fromTranslation(Mat4(), Vec3.inverse(Vec3(), data.principalAxes.center))
+    const matrix = Mat4.fromTranslation(Mat4(), Vec3.inverse(Vec3(), origin))
 
 
     const cage = createCage(vertices, edges)
     const cage = createCage(vertices, edges)
-    const radius = (Math.cbrt(getVolume(data)) / 300) * props.orientationScale
+    const volume = Axes3D.volume(data.principalAxes.boxAxes)
+    const radius = (Math.cbrt(volume) / 300) * props.scale
     state.currentGroup = VisualGroup.OrientedBox
     state.currentGroup = VisualGroup.OrientedBox
     MeshBuilder.addCage(state, matrix, cage, radius, 2, 20)
     MeshBuilder.addCage(state, matrix, cage, radius, 2, 20)
 }
 }
@@ -122,7 +127,7 @@ function buildOrientedBox(state: MeshBuilder.State, data: OrientationData, props
 function getOrientationMesh(data: OrientationData, props: OrientationProps, mesh?: Mesh) {
 function getOrientationMesh(data: OrientationData, props: OrientationProps, mesh?: Mesh) {
     const state = MeshBuilder.createState(256, 128, mesh)
     const state = MeshBuilder.createState(256, 128, mesh)
 
 
-    if (props.visuals.includes('principal-axes')) buildPrincipalAxes(state, data, props)
+    if (props.visuals.includes('principal-axes')) buildMomentsAxes(state, data, props)
     if (props.visuals.includes('oriented-box')) buildOrientedBox(state, data, props)
     if (props.visuals.includes('oriented-box')) buildOrientedBox(state, data, props)
 
 
     return MeshBuilder.getMesh(state)
     return MeshBuilder.getMesh(state)
@@ -137,10 +142,8 @@ function getLabel(structure: Structure) {
 export async function getStructureOrientationRepresentation(ctx: RuntimeContext, structure: Structure, params: OrientationProps, prev?: ShapeRepresentation<OrientationData, Mesh, Mesh.Params>) {
 export async function getStructureOrientationRepresentation(ctx: RuntimeContext, structure: Structure, params: OrientationProps, prev?: ShapeRepresentation<OrientationData, Mesh, Mesh.Params>) {
     const repr = prev || ShapeRepresentation(getOrientationShape, Mesh.Utils);
     const repr = prev || ShapeRepresentation(getOrientationShape, Mesh.Utils);
     const label = getLabel(structure)
     const label = getLabel(structure)
-    const positions = getPositions(structure)
-    const principalAxes = PrincipalAxes.ofPoints(Matrix.fromArray(positions, 3, structure.elementCount))
-    const projectedScale = PrincipalAxes.getProjectedScale(positions, principalAxes)
-    const data: OrientationData = { label, principalAxes, projectedScale }
+    const principalAxes = PrincipalAxes.ofPositions(getPositions(structure))
+    const data: OrientationData = { label, principalAxes }
     await repr.createOrUpdate(params, data).runInContext(ctx);
     await repr.createOrUpdate(params, data).runInContext(ctx);
     return repr;
     return repr;
 }
 }
@@ -158,5 +161,5 @@ function getOrientationShape(ctx: RuntimeContext, data: OrientationData, props:
     const getLabel = function (groupId: number ) {
     const getLabel = function (groupId: number ) {
         return `${getOrientationLabel(data, groupId)} of ${data.label}`
         return `${getOrientationLabel(data, groupId)} of ${data.label}`
     }
     }
-    return Shape.create('Principal Axes', data, geo, () => props.orientationColor, () => 1, getLabel)
+    return Shape.create('Principal Axes', data, geo, () => props.color, () => 1, getLabel)
 }
 }